Compare commits
20 Commits
docs/auto-
...
docs/auto-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80929711be | ||
|
|
1a3a528bf7 | ||
|
|
c164adc6ff | ||
|
|
9d61e0447d | ||
|
|
ee11b735b3 | ||
|
|
6d978228d9 | ||
|
|
ea9341e7af | ||
|
|
4296e383ea | ||
|
|
97b2781709 | ||
|
|
96553e4a5f | ||
|
|
7582219365 | ||
|
|
84baedc3d2 | ||
|
|
78da39edff | ||
|
|
4d1416b175 | ||
|
|
dc811eb45e | ||
|
|
3c41a113fe | ||
|
|
0e8c42c7cb | ||
|
|
799d1d2cce | ||
|
|
e81040def5 | ||
|
|
597f6b03b4 |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
docs(move): clarify cross-tag move docs; deprecate "force"; add explicit --with-dependencies/--ignore-dependencies examples
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Restore Taskmaster claude-code commands and move clear commands under /remove to avoid collision with the claude-code /clear command.
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Enhanced Gemini CLI provider with codebase-aware task generation
|
||||
|
||||
Added automatic codebase analysis for Gemini CLI provider in parse-prd, and analyze-complexity, add-task, udpate-task, update, update-subtask commands
|
||||
When using Gemini CLI as the AI provider, Task Master now instructs the AI to analyze the project structure, existing implementations, and patterns before generating tasks or subtasks
|
||||
Tasks and subtasks generated by Claude Code are now informed by actual codebase analysis, resulting in more accurate and contextual outputs
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"mode": "exit",
|
||||
"tag": "rc",
|
||||
"initialVersions": {
|
||||
"task-master-ai": "0.25.1",
|
||||
"docs": "0.0.1",
|
||||
"extension": "0.24.1"
|
||||
},
|
||||
"changesets": [
|
||||
"clarify-force-move-docs",
|
||||
"curvy-moons-dig",
|
||||
"sour-coins-lay",
|
||||
"strong-eagles-vanish",
|
||||
"wet-candies-accept"
|
||||
]
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Add configurable codebase analysis feature flag with multiple configuration sources
|
||||
|
||||
Users can now control whether codebase analysis features (Claude Code and Gemini CLI integration) are enabled through environment variables, MCP configuration, or project config files.
|
||||
|
||||
Priority order: .env > MCP session env > .taskmaster/config.json.
|
||||
|
||||
Set `TASKMASTER_ENABLE_CODEBASE_ANALYSIS=false` in `.env` to disable codebase analysis prompts and tool integration.
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
feat(move): improve cross-tag move UX and safety
|
||||
|
||||
- CLI: print "Next Steps" tips after cross-tag moves that used --ignore-dependencies (validate/fix guidance)
|
||||
- CLI: show dedicated help block on ID collisions (destination tag already has the ID)
|
||||
- Core: add structured suggestions to TASK_ALREADY_EXISTS errors
|
||||
- MCP: map ID collision errors to TASK_ALREADY_EXISTS and include suggestions
|
||||
- Tests: cover MCP options, error suggestions, CLI tips printing, and integration error payload suggestions
|
||||
---
|
||||
@@ -1,14 +0,0 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Enhanced Claude Code and Google CLI integration with automatic codebase analysis for task operations
|
||||
|
||||
When using Claude Code as the AI provider, task management commands now automatically analyze your codebase before generating or updating tasks. This provides more accurate, context-aware implementation details that align with your project's existing architecture and patterns.
|
||||
|
||||
Commands contextualised:
|
||||
|
||||
- add-task
|
||||
- update-subtask
|
||||
- update-task
|
||||
- update
|
||||
24
.github/workflows/ci.yml
vendored
24
.github/workflows/ci.yml
vendored
@@ -11,6 +11,10 @@ on:
|
||||
- next
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -61,7 +65,7 @@ jobs:
|
||||
timeout-minutes: 5
|
||||
|
||||
- name: Typecheck
|
||||
run: npm run typecheck
|
||||
run: npm run turbo:typecheck
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
@@ -84,11 +88,18 @@ jobs:
|
||||
timeout-minutes: 5
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
run: npm run turbo:build
|
||||
env:
|
||||
NODE_ENV: production
|
||||
FORCE_COLOR: 1
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-artifacts
|
||||
path: dist/
|
||||
retention-days: 1
|
||||
|
||||
test:
|
||||
name: Test
|
||||
timeout-minutes: 15
|
||||
@@ -108,10 +119,11 @@ jobs:
|
||||
run: npm install --frozen-lockfile --prefer-offline
|
||||
timeout-minutes: 5
|
||||
|
||||
- name: Build packages (required for tests)
|
||||
run: npm run build:packages
|
||||
env:
|
||||
NODE_ENV: production
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build-artifacts
|
||||
path: dist/
|
||||
|
||||
- name: Run Tests
|
||||
run: |
|
||||
|
||||
43
CHANGELOG.md
43
CHANGELOG.md
@@ -1,5 +1,48 @@
|
||||
# task-master-ai
|
||||
|
||||
## 0.26.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#1133](https://github.com/eyaltoledano/claude-task-master/pull/1133) [`df26c65`](https://github.com/eyaltoledano/claude-task-master/commit/df26c65632000874a73504963b08f18c46283144) Thanks [@neonwatty](https://github.com/neonwatty)! - Restore Taskmaster claude-code commands and move clear commands under /remove to avoid collision with the claude-code /clear command.
|
||||
|
||||
- [#1163](https://github.com/eyaltoledano/claude-task-master/pull/1163) [`37af0f1`](https://github.com/eyaltoledano/claude-task-master/commit/37af0f191227a68d119b7f89a377bf932ee3ac66) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Enhanced Gemini CLI provider with codebase-aware task generation
|
||||
|
||||
Added automatic codebase analysis for Gemini CLI provider in parse-prd, and analyze-complexity, add-task, udpate-task, update, update-subtask commands
|
||||
When using Gemini CLI as the AI provider, Task Master now instructs the AI to analyze the project structure, existing implementations, and patterns before generating tasks or subtasks
|
||||
Tasks and subtasks generated by Claude Code are now informed by actual codebase analysis, resulting in more accurate and contextual outputs
|
||||
|
||||
- [#1165](https://github.com/eyaltoledano/claude-task-master/pull/1165) [`c4f92f6`](https://github.com/eyaltoledano/claude-task-master/commit/c4f92f6a0aee3435c56eb8d27d9aa9204284833e) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Add configurable codebase analysis feature flag with multiple configuration sources
|
||||
|
||||
Users can now control whether codebase analysis features (Claude Code and Gemini CLI integration) are enabled through environment variables, MCP configuration, or project config files.
|
||||
|
||||
Priority order: .env > MCP session env > .taskmaster/config.json.
|
||||
|
||||
Set `TASKMASTER_ENABLE_CODEBASE_ANALYSIS=false` in `.env` to disable codebase analysis prompts and tool integration.
|
||||
|
||||
- [#1135](https://github.com/eyaltoledano/claude-task-master/pull/1135) [`8783708`](https://github.com/eyaltoledano/claude-task-master/commit/8783708e5e3389890a78fcf685d3da0580e73b3f) Thanks [@mm-parthy](https://github.com/mm-parthy)! - feat(move): improve cross-tag move UX and safety
|
||||
- CLI: print "Next Steps" tips after cross-tag moves that used --ignore-dependencies (validate/fix guidance)
|
||||
- CLI: show dedicated help block on ID collisions (destination tag already has the ID)
|
||||
- Core: add structured suggestions to TASK_ALREADY_EXISTS errors
|
||||
- MCP: map ID collision errors to TASK_ALREADY_EXISTS and include suggestions
|
||||
- Tests: cover MCP options, error suggestions, CLI tips printing, and integration error payload suggestions
|
||||
|
||||
***
|
||||
|
||||
- [#1162](https://github.com/eyaltoledano/claude-task-master/pull/1162) [`4dad2fd`](https://github.com/eyaltoledano/claude-task-master/commit/4dad2fd613ceac56a65ae9d3c1c03092b8860ac9) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Enhanced Claude Code and Google CLI integration with automatic codebase analysis for task operations
|
||||
|
||||
When using Claude Code as the AI provider, task management commands now automatically analyze your codebase before generating or updating tasks. This provides more accurate, context-aware implementation details that align with your project's existing architecture and patterns.
|
||||
|
||||
Commands contextualised:
|
||||
- add-task
|
||||
- update-subtask
|
||||
- update-task
|
||||
- update
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1135](https://github.com/eyaltoledano/claude-task-master/pull/1135) [`8783708`](https://github.com/eyaltoledano/claude-task-master/commit/8783708e5e3389890a78fcf685d3da0580e73b3f) Thanks [@mm-parthy](https://github.com/mm-parthy)! - docs(move): clarify cross-tag move docs; deprecate "force"; add explicit --with-dependencies/--ignore-dependencies examples
|
||||
|
||||
## 0.26.0-rc.1
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -6,15 +6,10 @@
|
||||
"main": "./dist/index.js",
|
||||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"files": ["dist", "README.md"],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint": "biome check src",
|
||||
"format": "biome format --write src",
|
||||
@@ -48,5 +43,10 @@
|
||||
},
|
||||
"keywords": ["task-master", "cli", "task-management", "productivity"],
|
||||
"author": "",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": ["src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,18 @@ import {
|
||||
} from '@tm/core';
|
||||
import type { StorageType } from '@tm/core/types';
|
||||
import * as ui from '../utils/ui.js';
|
||||
import {
|
||||
displayHeader,
|
||||
displayDashboards,
|
||||
calculateTaskStatistics,
|
||||
calculateSubtaskStatistics,
|
||||
calculateDependencyStatistics,
|
||||
getPriorityBreakdown,
|
||||
displayRecommendedNextTask,
|
||||
getTaskDescription,
|
||||
displaySuggestedNextSteps,
|
||||
type NextTaskInfo
|
||||
} from '../ui/index.js';
|
||||
|
||||
/**
|
||||
* Options interface for the list command
|
||||
@@ -245,19 +257,16 @@ export class ListTasksCommand extends Command {
|
||||
* Display in text format with tables
|
||||
*/
|
||||
private displayText(data: ListTasksResult, withSubtasks?: boolean): void {
|
||||
const { tasks, total, filtered, tag, storageType } = data;
|
||||
const { tasks, tag } = data;
|
||||
|
||||
// Header
|
||||
ui.displayBanner(`Task List${tag ? ` (${tag})` : ''}`);
|
||||
// Get file path for display
|
||||
const filePath = this.tmCore ? `.taskmaster/tasks/tasks.json` : undefined;
|
||||
|
||||
// Statistics
|
||||
console.log(chalk.blue.bold('\n📊 Statistics:\n'));
|
||||
console.log(` Total tasks: ${chalk.cyan(total)}`);
|
||||
console.log(` Filtered: ${chalk.cyan(filtered)}`);
|
||||
if (tag) {
|
||||
console.log(` Tag: ${chalk.cyan(tag)}`);
|
||||
}
|
||||
console.log(` Storage: ${chalk.cyan(storageType)}`);
|
||||
// Display header without banner (banner already shown by main CLI)
|
||||
displayHeader({
|
||||
tag: tag || 'master',
|
||||
filePath: filePath
|
||||
});
|
||||
|
||||
// No tasks message
|
||||
if (tasks.length === 0) {
|
||||
@@ -265,21 +274,50 @@ export class ListTasksCommand extends Command {
|
||||
return;
|
||||
}
|
||||
|
||||
// Task table
|
||||
console.log(chalk.blue.bold(`\n📋 Tasks (${tasks.length}):\n`));
|
||||
// Calculate statistics
|
||||
const taskStats = calculateTaskStatistics(tasks);
|
||||
const subtaskStats = calculateSubtaskStatistics(tasks);
|
||||
const depStats = calculateDependencyStatistics(tasks);
|
||||
const priorityBreakdown = getPriorityBreakdown(tasks);
|
||||
|
||||
// Find next task following the same logic as findNextTask
|
||||
const nextTask = this.findNextTask(tasks);
|
||||
|
||||
// Display dashboard boxes
|
||||
displayDashboards(
|
||||
taskStats,
|
||||
subtaskStats,
|
||||
priorityBreakdown,
|
||||
depStats,
|
||||
nextTask
|
||||
);
|
||||
|
||||
// Task table - no title, just show the table directly
|
||||
console.log(
|
||||
ui.createTaskTable(tasks, {
|
||||
showSubtasks: withSubtasks,
|
||||
showDependencies: true
|
||||
showDependencies: true,
|
||||
showComplexity: true // Enable complexity column
|
||||
})
|
||||
);
|
||||
|
||||
// Progress bar
|
||||
const completedCount = tasks.filter(
|
||||
(t: Task) => t.status === 'done'
|
||||
).length;
|
||||
console.log(chalk.blue.bold('\n📊 Overall Progress:\n'));
|
||||
console.log(` ${ui.createProgressBar(completedCount, tasks.length)}`);
|
||||
// Display recommended next task section immediately after table
|
||||
if (nextTask) {
|
||||
// Find the full task object to get description
|
||||
const fullTask = tasks.find((t) => String(t.id) === String(nextTask.id));
|
||||
const description = fullTask ? getTaskDescription(fullTask) : undefined;
|
||||
|
||||
displayRecommendedNextTask({
|
||||
...nextTask,
|
||||
status: 'pending', // Next task is typically pending
|
||||
description
|
||||
});
|
||||
} else {
|
||||
displayRecommendedNextTask(undefined);
|
||||
}
|
||||
|
||||
// Display suggested next steps at the end
|
||||
displaySuggestedNextSteps();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -289,6 +327,128 @@ export class ListTasksCommand extends Command {
|
||||
this.lastResult = result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the next task to work on
|
||||
* Implements the same logic as scripts/modules/task-manager/find-next-task.js
|
||||
*/
|
||||
private findNextTask(tasks: Task[]): NextTaskInfo | undefined {
|
||||
const priorityValues: Record<string, number> = {
|
||||
critical: 4,
|
||||
high: 3,
|
||||
medium: 2,
|
||||
low: 1
|
||||
};
|
||||
|
||||
// Build set of completed task IDs (including subtasks)
|
||||
const completedIds = new Set<string>();
|
||||
tasks.forEach((t) => {
|
||||
if (t.status === 'done' || t.status === 'completed') {
|
||||
completedIds.add(String(t.id));
|
||||
}
|
||||
if (t.subtasks) {
|
||||
t.subtasks.forEach((st) => {
|
||||
if (st.status === 'done' || st.status === 'completed') {
|
||||
completedIds.add(`${t.id}.${st.id}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// First, look for eligible subtasks in in-progress parent tasks
|
||||
const candidateSubtasks: NextTaskInfo[] = [];
|
||||
|
||||
tasks
|
||||
.filter(
|
||||
(t) => t.status === 'in-progress' && t.subtasks && t.subtasks.length > 0
|
||||
)
|
||||
.forEach((parent) => {
|
||||
parent.subtasks!.forEach((st) => {
|
||||
const stStatus = (st.status || 'pending').toLowerCase();
|
||||
if (stStatus !== 'pending' && stStatus !== 'in-progress') return;
|
||||
|
||||
// Check if dependencies are satisfied
|
||||
const fullDeps =
|
||||
st.dependencies?.map((d) => {
|
||||
// Handle both numeric and string IDs
|
||||
if (typeof d === 'string' && d.includes('.')) {
|
||||
return d;
|
||||
}
|
||||
return `${parent.id}.${d}`;
|
||||
}) ?? [];
|
||||
|
||||
const depsSatisfied =
|
||||
fullDeps.length === 0 ||
|
||||
fullDeps.every((depId) => completedIds.has(String(depId)));
|
||||
|
||||
if (depsSatisfied) {
|
||||
candidateSubtasks.push({
|
||||
id: `${parent.id}.${st.id}`,
|
||||
title: st.title || `Subtask ${st.id}`,
|
||||
priority: st.priority || parent.priority || 'medium',
|
||||
dependencies: fullDeps.map((d) => String(d))
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (candidateSubtasks.length > 0) {
|
||||
// Sort by priority, then by dependencies count, then by ID
|
||||
candidateSubtasks.sort((a, b) => {
|
||||
const pa = priorityValues[a.priority || 'medium'] ?? 2;
|
||||
const pb = priorityValues[b.priority || 'medium'] ?? 2;
|
||||
if (pb !== pa) return pb - pa;
|
||||
|
||||
const depCountA = a.dependencies?.length || 0;
|
||||
const depCountB = b.dependencies?.length || 0;
|
||||
if (depCountA !== depCountB) return depCountA - depCountB;
|
||||
|
||||
return String(a.id).localeCompare(String(b.id));
|
||||
});
|
||||
return candidateSubtasks[0];
|
||||
}
|
||||
|
||||
// Fall back to finding eligible top-level tasks
|
||||
const eligibleTasks = tasks.filter((task) => {
|
||||
// Skip non-eligible statuses
|
||||
const status = (task.status || 'pending').toLowerCase();
|
||||
if (status !== 'pending' && status !== 'in-progress') return false;
|
||||
|
||||
// Check dependencies
|
||||
const deps = task.dependencies || [];
|
||||
const depsSatisfied =
|
||||
deps.length === 0 ||
|
||||
deps.every((depId) => completedIds.has(String(depId)));
|
||||
|
||||
return depsSatisfied;
|
||||
});
|
||||
|
||||
if (eligibleTasks.length === 0) return undefined;
|
||||
|
||||
// Sort eligible tasks
|
||||
eligibleTasks.sort((a, b) => {
|
||||
// Priority (higher first)
|
||||
const pa = priorityValues[a.priority || 'medium'] ?? 2;
|
||||
const pb = priorityValues[b.priority || 'medium'] ?? 2;
|
||||
if (pb !== pa) return pb - pa;
|
||||
|
||||
// Dependencies count (fewer first)
|
||||
const depCountA = a.dependencies?.length || 0;
|
||||
const depCountB = b.dependencies?.length || 0;
|
||||
if (depCountA !== depCountB) return depCountA - depCountB;
|
||||
|
||||
// ID (lower first)
|
||||
return Number(a.id) - Number(b.id);
|
||||
});
|
||||
|
||||
const nextTask = eligibleTasks[0];
|
||||
return {
|
||||
id: nextTask.id,
|
||||
title: nextTask.title,
|
||||
priority: nextTask.priority,
|
||||
dependencies: nextTask.dependencies?.map((d) => String(d))
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last result (for programmatic usage)
|
||||
*/
|
||||
|
||||
@@ -9,6 +9,14 @@ import boxen from 'boxen';
|
||||
import { createTaskMasterCore, type Task, type TaskMasterCore } from '@tm/core';
|
||||
import type { StorageType } from '@tm/core/types';
|
||||
import * as ui from '../utils/ui.js';
|
||||
import {
|
||||
displayTaskHeader,
|
||||
displayTaskProperties,
|
||||
displayImplementationDetails,
|
||||
displayTestStrategy,
|
||||
displaySubtasks,
|
||||
displaySuggestedActions
|
||||
} from '../ui/components/task-detail.component.js';
|
||||
|
||||
/**
|
||||
* Options interface for the show command
|
||||
@@ -258,46 +266,26 @@ export class ShowCommand extends Command {
|
||||
|
||||
const task = result.task;
|
||||
|
||||
// Header
|
||||
console.log(
|
||||
boxen(chalk.white.bold(`Task #${task.id} - ${task.title}`), {
|
||||
padding: { top: 0, bottom: 0, left: 1, right: 1 },
|
||||
borderColor: 'blue',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1 }
|
||||
})
|
||||
);
|
||||
// Display header with tag
|
||||
displayTaskHeader(task.id, task.title);
|
||||
|
||||
// Task details
|
||||
console.log(
|
||||
`\n${chalk.blue.bold('Status:')} ${ui.getStatusWithColor(task.status)}`
|
||||
);
|
||||
console.log(
|
||||
`${chalk.blue.bold('Priority:')} ${ui.getPriorityWithColor(task.priority)}`
|
||||
);
|
||||
|
||||
if (task.description) {
|
||||
console.log(`\n${chalk.blue.bold('Description:')}`);
|
||||
console.log(task.description);
|
||||
}
|
||||
// Display task properties in table format
|
||||
displayTaskProperties(task);
|
||||
|
||||
// Display implementation details if available
|
||||
if (task.details) {
|
||||
console.log(`\n${chalk.blue.bold('Details:')}`);
|
||||
console.log(task.details);
|
||||
console.log(); // Empty line for spacing
|
||||
displayImplementationDetails(task.details);
|
||||
}
|
||||
|
||||
// Dependencies
|
||||
if (task.dependencies && task.dependencies.length > 0) {
|
||||
console.log(`\n${chalk.blue.bold('Dependencies:')}`);
|
||||
task.dependencies.forEach((dep) => {
|
||||
console.log(` - ${chalk.cyan(dep)}`);
|
||||
});
|
||||
// Display test strategy if available
|
||||
if ('testStrategy' in task && task.testStrategy) {
|
||||
console.log(); // Empty line for spacing
|
||||
displayTestStrategy(task.testStrategy as string);
|
||||
}
|
||||
|
||||
// Subtasks
|
||||
// Display subtasks if available
|
||||
if (task.subtasks && task.subtasks.length > 0) {
|
||||
console.log(`\n${chalk.blue.bold('Subtasks:')}`);
|
||||
|
||||
// Filter subtasks by status if provided
|
||||
const filteredSubtasks = options.status
|
||||
? task.subtasks.filter((sub) => sub.status === options.status)
|
||||
@@ -308,23 +296,12 @@ export class ShowCommand extends Command {
|
||||
chalk.gray(` No subtasks with status '${options.status}'`)
|
||||
);
|
||||
} else {
|
||||
filteredSubtasks.forEach((subtask) => {
|
||||
console.log(
|
||||
` ${chalk.cyan(`${task.id}.${subtask.id}`)} ${ui.getStatusWithColor(subtask.status)} ${subtask.title}`
|
||||
);
|
||||
if (subtask.description) {
|
||||
console.log(` ${chalk.gray(subtask.description)}`);
|
||||
}
|
||||
});
|
||||
displaySubtasks(filteredSubtasks, task.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (task.testStrategy) {
|
||||
console.log(`\n${chalk.blue.bold('Test Strategy:')}`);
|
||||
console.log(task.testStrategy);
|
||||
}
|
||||
|
||||
console.log(`\n${chalk.gray('Storage: ' + result.storageType)}`);
|
||||
// Display suggested actions
|
||||
displaySuggestedActions(task.id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
567
apps/cli/src/ui/components/dashboard.component.ts
Normal file
567
apps/cli/src/ui/components/dashboard.component.ts
Normal file
@@ -0,0 +1,567 @@
|
||||
/**
|
||||
* @fileoverview Dashboard components for Task Master CLI
|
||||
* Displays project statistics and dependency information
|
||||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
import boxen from 'boxen';
|
||||
import type { Task, TaskPriority } from '@tm/core/types';
|
||||
|
||||
/**
|
||||
* Statistics for task collection
|
||||
*/
|
||||
export interface TaskStatistics {
|
||||
total: number;
|
||||
done: number;
|
||||
inProgress: number;
|
||||
pending: number;
|
||||
blocked: number;
|
||||
deferred: number;
|
||||
cancelled: number;
|
||||
review?: number;
|
||||
completionPercentage: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Statistics for dependencies
|
||||
*/
|
||||
export interface DependencyStatistics {
|
||||
tasksWithNoDeps: number;
|
||||
tasksReadyToWork: number;
|
||||
tasksBlockedByDeps: number;
|
||||
mostDependedOnTaskId?: number;
|
||||
mostDependedOnCount?: number;
|
||||
avgDependenciesPerTask: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Next task information
|
||||
*/
|
||||
export interface NextTaskInfo {
|
||||
id: string | number;
|
||||
title: string;
|
||||
priority?: TaskPriority;
|
||||
dependencies?: (string | number)[];
|
||||
complexity?: number | string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Status breakdown for progress bars
|
||||
*/
|
||||
export interface StatusBreakdown {
|
||||
'in-progress'?: number;
|
||||
pending?: number;
|
||||
blocked?: number;
|
||||
deferred?: number;
|
||||
cancelled?: number;
|
||||
review?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a progress bar with color-coded status segments
|
||||
*/
|
||||
function createProgressBar(
|
||||
completionPercentage: number,
|
||||
width: number = 30,
|
||||
statusBreakdown?: StatusBreakdown
|
||||
): string {
|
||||
// If no breakdown provided, use simple green bar
|
||||
if (!statusBreakdown) {
|
||||
const filled = Math.round((completionPercentage / 100) * width);
|
||||
const empty = width - filled;
|
||||
return chalk.green('█').repeat(filled) + chalk.gray('░').repeat(empty);
|
||||
}
|
||||
|
||||
// Build the bar with different colored sections
|
||||
// Order matches the status display: Done, Cancelled, Deferred, In Progress, Review, Pending, Blocked
|
||||
let bar = '';
|
||||
let charsUsed = 0;
|
||||
|
||||
// 1. Green filled blocks for completed tasks (done)
|
||||
const completedChars = Math.round((completionPercentage / 100) * width);
|
||||
if (completedChars > 0) {
|
||||
bar += chalk.green('█').repeat(completedChars);
|
||||
charsUsed += completedChars;
|
||||
}
|
||||
|
||||
// 2. Gray filled blocks for cancelled (won't be done)
|
||||
if (statusBreakdown.cancelled && charsUsed < width) {
|
||||
const cancelledChars = Math.round(
|
||||
(statusBreakdown.cancelled / 100) * width
|
||||
);
|
||||
const actualChars = Math.min(cancelledChars, width - charsUsed);
|
||||
if (actualChars > 0) {
|
||||
bar += chalk.gray('█').repeat(actualChars);
|
||||
charsUsed += actualChars;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Gray filled blocks for deferred (won't be done now)
|
||||
if (statusBreakdown.deferred && charsUsed < width) {
|
||||
const deferredChars = Math.round((statusBreakdown.deferred / 100) * width);
|
||||
const actualChars = Math.min(deferredChars, width - charsUsed);
|
||||
if (actualChars > 0) {
|
||||
bar += chalk.gray('█').repeat(actualChars);
|
||||
charsUsed += actualChars;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Blue filled blocks for in-progress (actively working)
|
||||
if (statusBreakdown['in-progress'] && charsUsed < width) {
|
||||
const inProgressChars = Math.round(
|
||||
(statusBreakdown['in-progress'] / 100) * width
|
||||
);
|
||||
const actualChars = Math.min(inProgressChars, width - charsUsed);
|
||||
if (actualChars > 0) {
|
||||
bar += chalk.blue('█').repeat(actualChars);
|
||||
charsUsed += actualChars;
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Magenta empty blocks for review (almost done)
|
||||
if (statusBreakdown.review && charsUsed < width) {
|
||||
const reviewChars = Math.round((statusBreakdown.review / 100) * width);
|
||||
const actualChars = Math.min(reviewChars, width - charsUsed);
|
||||
if (actualChars > 0) {
|
||||
bar += chalk.magenta('░').repeat(actualChars);
|
||||
charsUsed += actualChars;
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Yellow empty blocks for pending (ready to start)
|
||||
if (statusBreakdown.pending && charsUsed < width) {
|
||||
const pendingChars = Math.round((statusBreakdown.pending / 100) * width);
|
||||
const actualChars = Math.min(pendingChars, width - charsUsed);
|
||||
if (actualChars > 0) {
|
||||
bar += chalk.yellow('░').repeat(actualChars);
|
||||
charsUsed += actualChars;
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Red empty blocks for blocked (can't start yet)
|
||||
if (statusBreakdown.blocked && charsUsed < width) {
|
||||
const blockedChars = Math.round((statusBreakdown.blocked / 100) * width);
|
||||
const actualChars = Math.min(blockedChars, width - charsUsed);
|
||||
if (actualChars > 0) {
|
||||
bar += chalk.red('░').repeat(actualChars);
|
||||
charsUsed += actualChars;
|
||||
}
|
||||
}
|
||||
|
||||
// Fill any remaining space with gray empty yellow blocks
|
||||
if (charsUsed < width) {
|
||||
bar += chalk.yellow('░').repeat(width - charsUsed);
|
||||
}
|
||||
|
||||
return bar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate task statistics from a list of tasks
|
||||
*/
|
||||
export function calculateTaskStatistics(tasks: Task[]): TaskStatistics {
|
||||
const stats: TaskStatistics = {
|
||||
total: tasks.length,
|
||||
done: 0,
|
||||
inProgress: 0,
|
||||
pending: 0,
|
||||
blocked: 0,
|
||||
deferred: 0,
|
||||
cancelled: 0,
|
||||
review: 0,
|
||||
completionPercentage: 0
|
||||
};
|
||||
|
||||
tasks.forEach((task) => {
|
||||
switch (task.status) {
|
||||
case 'done':
|
||||
stats.done++;
|
||||
break;
|
||||
case 'in-progress':
|
||||
stats.inProgress++;
|
||||
break;
|
||||
case 'pending':
|
||||
stats.pending++;
|
||||
break;
|
||||
case 'blocked':
|
||||
stats.blocked++;
|
||||
break;
|
||||
case 'deferred':
|
||||
stats.deferred++;
|
||||
break;
|
||||
case 'cancelled':
|
||||
stats.cancelled++;
|
||||
break;
|
||||
case 'review':
|
||||
stats.review = (stats.review || 0) + 1;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
stats.completionPercentage =
|
||||
stats.total > 0 ? Math.round((stats.done / stats.total) * 100) : 0;
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate subtask statistics from tasks
|
||||
*/
|
||||
export function calculateSubtaskStatistics(tasks: Task[]): TaskStatistics {
|
||||
const stats: TaskStatistics = {
|
||||
total: 0,
|
||||
done: 0,
|
||||
inProgress: 0,
|
||||
pending: 0,
|
||||
blocked: 0,
|
||||
deferred: 0,
|
||||
cancelled: 0,
|
||||
review: 0,
|
||||
completionPercentage: 0
|
||||
};
|
||||
|
||||
tasks.forEach((task) => {
|
||||
if (task.subtasks && task.subtasks.length > 0) {
|
||||
task.subtasks.forEach((subtask) => {
|
||||
stats.total++;
|
||||
switch (subtask.status) {
|
||||
case 'done':
|
||||
stats.done++;
|
||||
break;
|
||||
case 'in-progress':
|
||||
stats.inProgress++;
|
||||
break;
|
||||
case 'pending':
|
||||
stats.pending++;
|
||||
break;
|
||||
case 'blocked':
|
||||
stats.blocked++;
|
||||
break;
|
||||
case 'deferred':
|
||||
stats.deferred++;
|
||||
break;
|
||||
case 'cancelled':
|
||||
stats.cancelled++;
|
||||
break;
|
||||
case 'review':
|
||||
stats.review = (stats.review || 0) + 1;
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
stats.completionPercentage =
|
||||
stats.total > 0 ? Math.round((stats.done / stats.total) * 100) : 0;
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate dependency statistics
|
||||
*/
|
||||
export function calculateDependencyStatistics(
|
||||
tasks: Task[]
|
||||
): DependencyStatistics {
|
||||
const completedTaskIds = new Set(
|
||||
tasks.filter((t) => t.status === 'done').map((t) => t.id)
|
||||
);
|
||||
|
||||
const tasksWithNoDeps = tasks.filter(
|
||||
(t) =>
|
||||
t.status !== 'done' && (!t.dependencies || t.dependencies.length === 0)
|
||||
).length;
|
||||
|
||||
const tasksWithAllDepsSatisfied = tasks.filter(
|
||||
(t) =>
|
||||
t.status !== 'done' &&
|
||||
t.dependencies &&
|
||||
t.dependencies.length > 0 &&
|
||||
t.dependencies.every((depId) => completedTaskIds.has(depId))
|
||||
).length;
|
||||
|
||||
const tasksBlockedByDeps = tasks.filter(
|
||||
(t) =>
|
||||
t.status !== 'done' &&
|
||||
t.dependencies &&
|
||||
t.dependencies.length > 0 &&
|
||||
!t.dependencies.every((depId) => completedTaskIds.has(depId))
|
||||
).length;
|
||||
|
||||
// Calculate most depended-on task
|
||||
const dependencyCount: Record<string, number> = {};
|
||||
tasks.forEach((task) => {
|
||||
if (task.dependencies && task.dependencies.length > 0) {
|
||||
task.dependencies.forEach((depId) => {
|
||||
const key = String(depId);
|
||||
dependencyCount[key] = (dependencyCount[key] || 0) + 1;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let mostDependedOnTaskId: number | undefined;
|
||||
let mostDependedOnCount = 0;
|
||||
|
||||
for (const [taskId, count] of Object.entries(dependencyCount)) {
|
||||
if (count > mostDependedOnCount) {
|
||||
mostDependedOnCount = count;
|
||||
mostDependedOnTaskId = parseInt(taskId);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate average dependencies
|
||||
const totalDependencies = tasks.reduce(
|
||||
(sum, task) => sum + (task.dependencies ? task.dependencies.length : 0),
|
||||
0
|
||||
);
|
||||
const avgDependenciesPerTask =
|
||||
tasks.length > 0 ? totalDependencies / tasks.length : 0;
|
||||
|
||||
return {
|
||||
tasksWithNoDeps,
|
||||
tasksReadyToWork: tasksWithNoDeps + tasksWithAllDepsSatisfied,
|
||||
tasksBlockedByDeps,
|
||||
mostDependedOnTaskId,
|
||||
mostDependedOnCount,
|
||||
avgDependenciesPerTask
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get priority counts
|
||||
*/
|
||||
export function getPriorityBreakdown(
|
||||
tasks: Task[]
|
||||
): Record<TaskPriority, number> {
|
||||
const breakdown: Record<TaskPriority, number> = {
|
||||
critical: 0,
|
||||
high: 0,
|
||||
medium: 0,
|
||||
low: 0
|
||||
};
|
||||
|
||||
tasks.forEach((task) => {
|
||||
const priority = task.priority || 'medium';
|
||||
breakdown[priority]++;
|
||||
});
|
||||
|
||||
return breakdown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate status breakdown as percentages
|
||||
*/
|
||||
function calculateStatusBreakdown(stats: TaskStatistics): StatusBreakdown {
|
||||
if (stats.total === 0) return {};
|
||||
|
||||
return {
|
||||
'in-progress': (stats.inProgress / stats.total) * 100,
|
||||
pending: (stats.pending / stats.total) * 100,
|
||||
blocked: (stats.blocked / stats.total) * 100,
|
||||
deferred: (stats.deferred / stats.total) * 100,
|
||||
cancelled: (stats.cancelled / stats.total) * 100,
|
||||
review: ((stats.review || 0) / stats.total) * 100
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Format status counts in the correct order with colors
|
||||
* @param stats - The statistics object containing counts
|
||||
* @param isSubtask - Whether this is for subtasks (affects "Done" vs "Completed" label)
|
||||
*/
|
||||
function formatStatusLine(
|
||||
stats: TaskStatistics,
|
||||
isSubtask: boolean = false
|
||||
): string {
|
||||
const parts: string[] = [];
|
||||
|
||||
// Order: Done, Cancelled, Deferred, In Progress, Review, Pending, Blocked
|
||||
if (isSubtask) {
|
||||
parts.push(`Completed: ${chalk.green(`${stats.done}/${stats.total}`)}`);
|
||||
} else {
|
||||
parts.push(`Done: ${chalk.green(stats.done)}`);
|
||||
}
|
||||
|
||||
parts.push(`Cancelled: ${chalk.gray(stats.cancelled)}`);
|
||||
parts.push(`Deferred: ${chalk.gray(stats.deferred)}`);
|
||||
|
||||
// Add line break for second row
|
||||
const firstLine = parts.join(' ');
|
||||
parts.length = 0;
|
||||
|
||||
parts.push(`In Progress: ${chalk.blue(stats.inProgress)}`);
|
||||
parts.push(`Review: ${chalk.magenta(stats.review || 0)}`);
|
||||
parts.push(`Pending: ${chalk.yellow(stats.pending)}`);
|
||||
parts.push(`Blocked: ${chalk.red(stats.blocked)}`);
|
||||
|
||||
const secondLine = parts.join(' ');
|
||||
|
||||
return firstLine + '\n' + secondLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the project dashboard box
|
||||
*/
|
||||
export function displayProjectDashboard(
|
||||
taskStats: TaskStatistics,
|
||||
subtaskStats: TaskStatistics,
|
||||
priorityBreakdown: Record<TaskPriority, number>
|
||||
): string {
|
||||
// Calculate status breakdowns using the helper function
|
||||
const taskStatusBreakdown = calculateStatusBreakdown(taskStats);
|
||||
const subtaskStatusBreakdown = calculateStatusBreakdown(subtaskStats);
|
||||
|
||||
// Create progress bars with the breakdowns
|
||||
const taskProgressBar = createProgressBar(
|
||||
taskStats.completionPercentage,
|
||||
30,
|
||||
taskStatusBreakdown
|
||||
);
|
||||
const subtaskProgressBar = createProgressBar(
|
||||
subtaskStats.completionPercentage,
|
||||
30,
|
||||
subtaskStatusBreakdown
|
||||
);
|
||||
|
||||
const taskPercentage = `${taskStats.completionPercentage}% ${taskStats.done}/${taskStats.total}`;
|
||||
const subtaskPercentage = `${subtaskStats.completionPercentage}% ${subtaskStats.done}/${subtaskStats.total}`;
|
||||
|
||||
const content =
|
||||
chalk.white.bold('Project Dashboard') +
|
||||
'\n' +
|
||||
`Tasks Progress: ${taskProgressBar} ${chalk.yellow(taskPercentage)}\n` +
|
||||
formatStatusLine(taskStats, false) +
|
||||
'\n\n' +
|
||||
`Subtasks Progress: ${subtaskProgressBar} ${chalk.cyan(subtaskPercentage)}\n` +
|
||||
formatStatusLine(subtaskStats, true) +
|
||||
'\n\n' +
|
||||
chalk.cyan.bold('Priority Breakdown:') +
|
||||
'\n' +
|
||||
`${chalk.red('•')} ${chalk.white('High priority:')} ${priorityBreakdown.high}\n` +
|
||||
`${chalk.yellow('•')} ${chalk.white('Medium priority:')} ${priorityBreakdown.medium}\n` +
|
||||
`${chalk.green('•')} ${chalk.white('Low priority:')} ${priorityBreakdown.low}`;
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the dependency dashboard box
|
||||
*/
|
||||
export function displayDependencyDashboard(
|
||||
depStats: DependencyStatistics,
|
||||
nextTask?: NextTaskInfo
|
||||
): string {
|
||||
const content =
|
||||
chalk.white.bold('Dependency Status & Next Task') +
|
||||
'\n' +
|
||||
chalk.cyan.bold('Dependency Metrics:') +
|
||||
'\n' +
|
||||
`${chalk.green('•')} ${chalk.white('Tasks with no dependencies:')} ${depStats.tasksWithNoDeps}\n` +
|
||||
`${chalk.green('•')} ${chalk.white('Tasks ready to work on:')} ${depStats.tasksReadyToWork}\n` +
|
||||
`${chalk.yellow('•')} ${chalk.white('Tasks blocked by dependencies:')} ${depStats.tasksBlockedByDeps}\n` +
|
||||
`${chalk.magenta('•')} ${chalk.white('Most depended-on task:')} ${
|
||||
depStats.mostDependedOnTaskId
|
||||
? chalk.cyan(
|
||||
`#${depStats.mostDependedOnTaskId} (${depStats.mostDependedOnCount} dependents)`
|
||||
)
|
||||
: chalk.gray('None')
|
||||
}\n` +
|
||||
`${chalk.blue('•')} ${chalk.white('Avg dependencies per task:')} ${depStats.avgDependenciesPerTask.toFixed(1)}\n\n` +
|
||||
chalk.cyan.bold('Next Task to Work On:') +
|
||||
'\n' +
|
||||
`ID: ${nextTask ? chalk.cyan(String(nextTask.id)) : chalk.gray('N/A')} - ${
|
||||
nextTask
|
||||
? chalk.white.bold(nextTask.title)
|
||||
: chalk.yellow('No task available')
|
||||
}\n` +
|
||||
`Priority: ${nextTask?.priority || chalk.gray('N/A')} Dependencies: ${
|
||||
nextTask?.dependencies?.length
|
||||
? chalk.cyan(nextTask.dependencies.join(', '))
|
||||
: chalk.gray('None')
|
||||
}\n` +
|
||||
`Complexity: ${nextTask?.complexity || chalk.gray('N/A')}`;
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display dashboard boxes side by side or stacked
|
||||
*/
|
||||
export function displayDashboards(
|
||||
taskStats: TaskStatistics,
|
||||
subtaskStats: TaskStatistics,
|
||||
priorityBreakdown: Record<TaskPriority, number>,
|
||||
depStats: DependencyStatistics,
|
||||
nextTask?: NextTaskInfo
|
||||
): void {
|
||||
const projectDashboardContent = displayProjectDashboard(
|
||||
taskStats,
|
||||
subtaskStats,
|
||||
priorityBreakdown
|
||||
);
|
||||
const dependencyDashboardContent = displayDependencyDashboard(
|
||||
depStats,
|
||||
nextTask
|
||||
);
|
||||
|
||||
// Get terminal width
|
||||
const terminalWidth = process.stdout.columns || 80;
|
||||
const minDashboardWidth = 50;
|
||||
const minDependencyWidth = 50;
|
||||
const totalMinWidth = minDashboardWidth + minDependencyWidth + 4;
|
||||
|
||||
// If terminal is wide enough, show side by side
|
||||
if (terminalWidth >= totalMinWidth) {
|
||||
const halfWidth = Math.floor(terminalWidth / 2);
|
||||
const boxContentWidth = halfWidth - 4;
|
||||
|
||||
const dashboardBox = boxen(projectDashboardContent, {
|
||||
padding: 1,
|
||||
borderColor: 'blue',
|
||||
borderStyle: 'round',
|
||||
width: boxContentWidth,
|
||||
dimBorder: false
|
||||
});
|
||||
|
||||
const dependencyBox = boxen(dependencyDashboardContent, {
|
||||
padding: 1,
|
||||
borderColor: 'magenta',
|
||||
borderStyle: 'round',
|
||||
width: boxContentWidth,
|
||||
dimBorder: false
|
||||
});
|
||||
|
||||
// Create side-by-side layout
|
||||
const dashboardLines = dashboardBox.split('\n');
|
||||
const dependencyLines = dependencyBox.split('\n');
|
||||
const maxHeight = Math.max(dashboardLines.length, dependencyLines.length);
|
||||
|
||||
const combinedLines = [];
|
||||
for (let i = 0; i < maxHeight; i++) {
|
||||
const dashLine = i < dashboardLines.length ? dashboardLines[i] : '';
|
||||
const depLine = i < dependencyLines.length ? dependencyLines[i] : '';
|
||||
const paddedDashLine = dashLine.padEnd(halfWidth, ' ');
|
||||
combinedLines.push(paddedDashLine + depLine);
|
||||
}
|
||||
|
||||
console.log(combinedLines.join('\n'));
|
||||
} else {
|
||||
// Show stacked vertically
|
||||
const dashboardBox = boxen(projectDashboardContent, {
|
||||
padding: 1,
|
||||
borderColor: 'blue',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 0, bottom: 1 }
|
||||
});
|
||||
|
||||
const dependencyBox = boxen(dependencyDashboardContent, {
|
||||
padding: 1,
|
||||
borderColor: 'magenta',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 0, bottom: 1 }
|
||||
});
|
||||
|
||||
console.log(dashboardBox);
|
||||
console.log(dependencyBox);
|
||||
}
|
||||
}
|
||||
45
apps/cli/src/ui/components/header.component.ts
Normal file
45
apps/cli/src/ui/components/header.component.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* @fileoverview Task Master header component
|
||||
* Displays the banner, version, project info, and file path
|
||||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
|
||||
/**
|
||||
* Header configuration options
|
||||
*/
|
||||
export interface HeaderOptions {
|
||||
title?: string;
|
||||
tag?: string;
|
||||
filePath?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the Task Master header with project info
|
||||
*/
|
||||
export function displayHeader(options: HeaderOptions = {}): void {
|
||||
const { filePath, tag } = options;
|
||||
|
||||
// Display tag and file path info
|
||||
if (tag) {
|
||||
let tagInfo = '';
|
||||
|
||||
if (tag && tag !== 'master') {
|
||||
tagInfo = `🏷 tag: ${chalk.cyan(tag)}`;
|
||||
} else {
|
||||
tagInfo = `🏷 tag: ${chalk.cyan('master')}`;
|
||||
}
|
||||
|
||||
console.log(tagInfo);
|
||||
|
||||
if (filePath) {
|
||||
// Convert to absolute path if it's relative
|
||||
const absolutePath = filePath.startsWith('/')
|
||||
? filePath
|
||||
: `${process.cwd()}/${filePath}`;
|
||||
console.log(`Listing tasks from: ${chalk.dim(absolutePath)}`);
|
||||
}
|
||||
|
||||
console.log(); // Empty line for spacing
|
||||
}
|
||||
}
|
||||
9
apps/cli/src/ui/components/index.ts
Normal file
9
apps/cli/src/ui/components/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* @fileoverview UI components exports
|
||||
*/
|
||||
|
||||
export * from './header.component.js';
|
||||
export * from './dashboard.component.js';
|
||||
export * from './next-task.component.js';
|
||||
export * from './suggested-steps.component.js';
|
||||
export * from './task-detail.component.js';
|
||||
134
apps/cli/src/ui/components/next-task.component.ts
Normal file
134
apps/cli/src/ui/components/next-task.component.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* @fileoverview Next task recommendation component
|
||||
* Displays detailed information about the recommended next task
|
||||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
import boxen from 'boxen';
|
||||
import type { Task } from '@tm/core/types';
|
||||
|
||||
/**
|
||||
* Next task display options
|
||||
*/
|
||||
export interface NextTaskDisplayOptions {
|
||||
id: string | number;
|
||||
title: string;
|
||||
priority?: string;
|
||||
status?: string;
|
||||
dependencies?: (string | number)[];
|
||||
description?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the recommended next task section
|
||||
*/
|
||||
export function displayRecommendedNextTask(
|
||||
task: NextTaskDisplayOptions | undefined
|
||||
): void {
|
||||
if (!task) {
|
||||
// If no task available, show a message
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.yellow(
|
||||
'No tasks available to work on. All tasks are either completed, blocked by dependencies, or in progress.'
|
||||
),
|
||||
{
|
||||
padding: 1,
|
||||
borderStyle: 'round',
|
||||
borderColor: 'yellow',
|
||||
title: '⚠ NO TASKS AVAILABLE ⚠',
|
||||
titleAlignment: 'center'
|
||||
}
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build the content for the next task box
|
||||
const content = [];
|
||||
|
||||
// Task header with ID and title
|
||||
content.push(
|
||||
`🔥 ${chalk.hex('#FF8800').bold('Next Task to Work On:')} ${chalk.yellow(`#${task.id}`)}${chalk.hex('#FF8800').bold(` - ${task.title}`)}`
|
||||
);
|
||||
content.push('');
|
||||
|
||||
// Priority and Status line
|
||||
const statusLine = [];
|
||||
if (task.priority) {
|
||||
const priorityColor =
|
||||
task.priority === 'high'
|
||||
? chalk.red
|
||||
: task.priority === 'medium'
|
||||
? chalk.yellow
|
||||
: chalk.gray;
|
||||
statusLine.push(`Priority: ${priorityColor.bold(task.priority)}`);
|
||||
}
|
||||
if (task.status) {
|
||||
const statusDisplay =
|
||||
task.status === 'pending'
|
||||
? chalk.yellow('○ pending')
|
||||
: task.status === 'in-progress'
|
||||
? chalk.blue('▶ in-progress')
|
||||
: chalk.gray(task.status);
|
||||
statusLine.push(`Status: ${statusDisplay}`);
|
||||
}
|
||||
content.push(statusLine.join(' '));
|
||||
|
||||
// Dependencies
|
||||
const depsDisplay =
|
||||
!task.dependencies || task.dependencies.length === 0
|
||||
? chalk.gray('None')
|
||||
: chalk.cyan(task.dependencies.join(', '));
|
||||
content.push(`Dependencies: ${depsDisplay}`);
|
||||
|
||||
// Description if available
|
||||
if (task.description) {
|
||||
content.push('');
|
||||
content.push(`Description: ${chalk.white(task.description)}`);
|
||||
}
|
||||
|
||||
// Action commands
|
||||
content.push('');
|
||||
content.push(
|
||||
`${chalk.cyan('Start working:')} ${chalk.yellow(`task-master set-status --id=${task.id} --status=in-progress`)}`
|
||||
);
|
||||
content.push(
|
||||
`${chalk.cyan('View details:')} ${chalk.yellow(`task-master show ${task.id}`)}`
|
||||
);
|
||||
|
||||
// Display in a styled box with orange border
|
||||
console.log(
|
||||
boxen(content.join('\n'), {
|
||||
padding: 1,
|
||||
margin: { top: 1, bottom: 1 },
|
||||
borderStyle: 'round',
|
||||
borderColor: '#FFA500', // Orange color
|
||||
title: chalk.hex('#FFA500')('⚡ RECOMMENDED NEXT TASK ⚡'),
|
||||
titleAlignment: 'center',
|
||||
width: process.stdout.columns * 0.97,
|
||||
fullscreen: false
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get task description from the full task object
|
||||
*/
|
||||
export function getTaskDescription(task: Task): string | undefined {
|
||||
// Try to get description from the task
|
||||
// This could be from task.description or the first line of task.details
|
||||
if ('description' in task && task.description) {
|
||||
return task.description as string;
|
||||
}
|
||||
|
||||
if ('details' in task && task.details) {
|
||||
// Take first sentence or line from details
|
||||
const details = task.details as string;
|
||||
const firstLine = details.split('\n')[0];
|
||||
const firstSentence = firstLine.split('.')[0];
|
||||
return firstSentence;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
31
apps/cli/src/ui/components/suggested-steps.component.ts
Normal file
31
apps/cli/src/ui/components/suggested-steps.component.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @fileoverview Suggested next steps component
|
||||
* Displays helpful command suggestions at the end of the list
|
||||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
import boxen from 'boxen';
|
||||
|
||||
/**
|
||||
* Display suggested next steps section
|
||||
*/
|
||||
export function displaySuggestedNextSteps(): void {
|
||||
const steps = [
|
||||
`${chalk.cyan('1.')} Run ${chalk.yellow('task-master next')} to see what to work on next`,
|
||||
`${chalk.cyan('2.')} Run ${chalk.yellow('task-master expand --id=<id>')} to break down a task into subtasks`,
|
||||
`${chalk.cyan('3.')} Run ${chalk.yellow('task-master set-status --id=<id> --status=done')} to mark a task as complete`
|
||||
];
|
||||
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.white.bold('Suggested Next Steps:') + '\n\n' + steps.join('\n'),
|
||||
{
|
||||
padding: 1,
|
||||
margin: { top: 0, bottom: 1 },
|
||||
borderStyle: 'round',
|
||||
borderColor: 'gray',
|
||||
width: process.stdout.columns * 0.97
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
264
apps/cli/src/ui/components/task-detail.component.ts
Normal file
264
apps/cli/src/ui/components/task-detail.component.ts
Normal file
@@ -0,0 +1,264 @@
|
||||
/**
|
||||
* @fileoverview Task detail component for show command
|
||||
* Displays detailed task information in a structured format
|
||||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
import boxen from 'boxen';
|
||||
import Table from 'cli-table3';
|
||||
import { marked, MarkedExtension } from 'marked';
|
||||
import { markedTerminal } from 'marked-terminal';
|
||||
import type { Task } from '@tm/core/types';
|
||||
import { getStatusWithColor, getPriorityWithColor } from '../../utils/ui.js';
|
||||
|
||||
// Configure marked to use terminal renderer with subtle colors
|
||||
marked.use(
|
||||
markedTerminal({
|
||||
// More subtle colors that match the overall design
|
||||
code: (code: string) => {
|
||||
// Custom code block handler to preserve formatting
|
||||
return code
|
||||
.split('\n')
|
||||
.map((line) => ' ' + chalk.cyan(line))
|
||||
.join('\n');
|
||||
},
|
||||
blockquote: chalk.gray.italic,
|
||||
html: chalk.gray,
|
||||
heading: chalk.white.bold, // White bold for headings
|
||||
hr: chalk.gray,
|
||||
listitem: chalk.white, // White for list items
|
||||
paragraph: chalk.white, // White for paragraphs (default text color)
|
||||
strong: chalk.white.bold, // White bold for strong text
|
||||
em: chalk.white.italic, // White italic for emphasis
|
||||
codespan: chalk.cyan, // Cyan for inline code (no background)
|
||||
del: chalk.dim.strikethrough,
|
||||
link: chalk.blue,
|
||||
href: chalk.blue.underline,
|
||||
// Add more explicit code block handling
|
||||
showSectionPrefix: false,
|
||||
unescape: true,
|
||||
emoji: false,
|
||||
// Try to preserve whitespace in code blocks
|
||||
tab: 4,
|
||||
width: 120
|
||||
}) as MarkedExtension
|
||||
);
|
||||
|
||||
// Also set marked options to preserve whitespace
|
||||
marked.setOptions({
|
||||
breaks: true,
|
||||
gfm: true
|
||||
});
|
||||
|
||||
/**
|
||||
* Display the task header with tag
|
||||
*/
|
||||
export function displayTaskHeader(
|
||||
taskId: string | number,
|
||||
title: string
|
||||
): void {
|
||||
// Display task header box
|
||||
console.log(
|
||||
boxen(chalk.white.bold(`Task: #${taskId} - ${title}`), {
|
||||
padding: { top: 0, bottom: 0, left: 1, right: 1 },
|
||||
borderColor: 'blue',
|
||||
borderStyle: 'round'
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display task properties in a table format
|
||||
*/
|
||||
export function displayTaskProperties(task: Task): void {
|
||||
const terminalWidth = process.stdout.columns * 0.95 || 100;
|
||||
// Create table for task properties - simple 2-column layout
|
||||
const table = new Table({
|
||||
head: [],
|
||||
style: {
|
||||
head: [],
|
||||
border: ['grey']
|
||||
},
|
||||
colWidths: [
|
||||
Math.floor(terminalWidth * 0.2),
|
||||
Math.floor(terminalWidth * 0.8)
|
||||
],
|
||||
wordWrap: true
|
||||
});
|
||||
|
||||
const deps =
|
||||
task.dependencies && task.dependencies.length > 0
|
||||
? task.dependencies.map((d) => String(d)).join(', ')
|
||||
: 'None';
|
||||
|
||||
// Build the left column (labels) and right column (values)
|
||||
const labels = [
|
||||
chalk.cyan('ID:'),
|
||||
chalk.cyan('Title:'),
|
||||
chalk.cyan('Status:'),
|
||||
chalk.cyan('Priority:'),
|
||||
chalk.cyan('Dependencies:'),
|
||||
chalk.cyan('Complexity:'),
|
||||
chalk.cyan('Description:')
|
||||
].join('\n');
|
||||
|
||||
const values = [
|
||||
String(task.id),
|
||||
task.title,
|
||||
getStatusWithColor(task.status),
|
||||
getPriorityWithColor(task.priority),
|
||||
deps,
|
||||
'N/A',
|
||||
task.description || ''
|
||||
].join('\n');
|
||||
|
||||
table.push([labels, values]);
|
||||
|
||||
console.log(table.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Display implementation details in a box
|
||||
*/
|
||||
export function displayImplementationDetails(details: string): void {
|
||||
// Handle all escaped characters properly
|
||||
const cleanDetails = details
|
||||
.replace(/\\n/g, '\n') // Convert \n to actual newlines
|
||||
.replace(/\\t/g, '\t') // Convert \t to actual tabs
|
||||
.replace(/\\"/g, '"') // Convert \" to actual quotes
|
||||
.replace(/\\\\/g, '\\'); // Convert \\ to single backslash
|
||||
|
||||
const terminalWidth = process.stdout.columns * 0.95 || 100;
|
||||
|
||||
// Parse markdown to terminal-friendly format
|
||||
const markdownResult = marked(cleanDetails);
|
||||
const formattedDetails =
|
||||
typeof markdownResult === 'string' ? markdownResult.trim() : cleanDetails; // Fallback to original if Promise
|
||||
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.white.bold('Implementation Details:') + '\n\n' + formattedDetails,
|
||||
{
|
||||
padding: 1,
|
||||
borderStyle: 'round',
|
||||
borderColor: 'cyan', // Changed to cyan to match the original
|
||||
width: terminalWidth // Fixed width to match the original
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display test strategy in a box
|
||||
*/
|
||||
export function displayTestStrategy(testStrategy: string): void {
|
||||
// Handle all escaped characters properly (same as implementation details)
|
||||
const cleanStrategy = testStrategy
|
||||
.replace(/\\n/g, '\n') // Convert \n to actual newlines
|
||||
.replace(/\\t/g, '\t') // Convert \t to actual tabs
|
||||
.replace(/\\"/g, '"') // Convert \" to actual quotes
|
||||
.replace(/\\\\/g, '\\'); // Convert \\ to single backslash
|
||||
|
||||
const terminalWidth = process.stdout.columns * 0.95 || 100;
|
||||
|
||||
// Parse markdown to terminal-friendly format (same as implementation details)
|
||||
const markdownResult = marked(cleanStrategy);
|
||||
const formattedStrategy =
|
||||
typeof markdownResult === 'string' ? markdownResult.trim() : cleanStrategy; // Fallback to original if Promise
|
||||
|
||||
console.log(
|
||||
boxen(chalk.white.bold('Test Strategy:') + '\n\n' + formattedStrategy, {
|
||||
padding: 1,
|
||||
borderStyle: 'round',
|
||||
borderColor: 'cyan', // Changed to cyan to match implementation details
|
||||
width: terminalWidth
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display subtasks in a table format
|
||||
*/
|
||||
export function displaySubtasks(
|
||||
subtasks: Array<{
|
||||
id: string | number;
|
||||
title: string;
|
||||
status: any;
|
||||
description?: string;
|
||||
dependencies?: string[];
|
||||
}>,
|
||||
parentId: string | number
|
||||
): void {
|
||||
const terminalWidth = process.stdout.columns * 0.95 || 100;
|
||||
// Display subtasks header
|
||||
console.log(
|
||||
boxen(chalk.magenta.bold('Subtasks'), {
|
||||
padding: { top: 0, bottom: 0, left: 1, right: 1 },
|
||||
borderColor: 'magenta',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1, bottom: 0 }
|
||||
})
|
||||
);
|
||||
|
||||
// Create subtasks table
|
||||
const table = new Table({
|
||||
head: [
|
||||
chalk.magenta.bold('ID'),
|
||||
chalk.magenta.bold('Status'),
|
||||
chalk.magenta.bold('Title'),
|
||||
chalk.magenta.bold('Deps')
|
||||
],
|
||||
style: {
|
||||
head: [],
|
||||
border: ['grey']
|
||||
},
|
||||
colWidths: [
|
||||
Math.floor(terminalWidth * 0.1),
|
||||
Math.floor(terminalWidth * 0.15),
|
||||
Math.floor(terminalWidth * 0.6),
|
||||
Math.floor(terminalWidth * 0.15)
|
||||
],
|
||||
wordWrap: true
|
||||
});
|
||||
|
||||
subtasks.forEach((subtask) => {
|
||||
const subtaskId = `${parentId}.${subtask.id}`;
|
||||
|
||||
// Format dependencies
|
||||
const deps =
|
||||
subtask.dependencies && subtask.dependencies.length > 0
|
||||
? subtask.dependencies.join(', ')
|
||||
: 'None';
|
||||
|
||||
table.push([
|
||||
subtaskId,
|
||||
getStatusWithColor(subtask.status),
|
||||
subtask.title,
|
||||
deps
|
||||
]);
|
||||
});
|
||||
|
||||
console.log(table.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Display suggested actions
|
||||
*/
|
||||
export function displaySuggestedActions(taskId: string | number): void {
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.white.bold('Suggested Actions:') +
|
||||
'\n\n' +
|
||||
`${chalk.cyan('1.')} Run ${chalk.yellow(`task-master set-status --id=${taskId} --status=in-progress`)} to start working\n` +
|
||||
`${chalk.cyan('2.')} Run ${chalk.yellow(`task-master expand --id=${taskId}`)} to break down into subtasks\n` +
|
||||
`${chalk.cyan('3.')} Run ${chalk.yellow(`task-master update-task --id=${taskId} --prompt="..."`)} to update details`,
|
||||
{
|
||||
padding: 1,
|
||||
margin: { top: 1 },
|
||||
borderStyle: 'round',
|
||||
borderColor: 'green',
|
||||
width: process.stdout.columns * 0.95 || 100
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
9
apps/cli/src/ui/index.ts
Normal file
9
apps/cli/src/ui/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* @fileoverview Main UI exports
|
||||
*/
|
||||
|
||||
// Export all components
|
||||
export * from './components/index.js';
|
||||
|
||||
// Re-export existing UI utilities
|
||||
export * from '../utils/ui.js';
|
||||
@@ -18,19 +18,44 @@ export function getStatusWithColor(
|
||||
const statusConfig = {
|
||||
done: {
|
||||
color: chalk.green,
|
||||
icon: String.fromCharCode(8730),
|
||||
tableIcon: String.fromCharCode(8730)
|
||||
}, // √
|
||||
pending: { color: chalk.yellow, icon: 'o', tableIcon: 'o' },
|
||||
icon: '✓',
|
||||
tableIcon: '✓'
|
||||
},
|
||||
pending: {
|
||||
color: chalk.yellow,
|
||||
icon: '○',
|
||||
tableIcon: '○'
|
||||
},
|
||||
'in-progress': {
|
||||
color: chalk.hex('#FFA500'),
|
||||
icon: String.fromCharCode(9654),
|
||||
tableIcon: '>'
|
||||
}, // ▶
|
||||
deferred: { color: chalk.gray, icon: 'x', tableIcon: 'x' },
|
||||
blocked: { color: chalk.red, icon: '!', tableIcon: '!' },
|
||||
review: { color: chalk.magenta, icon: '?', tableIcon: '?' },
|
||||
cancelled: { color: chalk.gray, icon: 'X', tableIcon: 'X' }
|
||||
icon: '▶',
|
||||
tableIcon: '▶'
|
||||
},
|
||||
deferred: {
|
||||
color: chalk.gray,
|
||||
icon: 'x',
|
||||
tableIcon: 'x'
|
||||
},
|
||||
review: {
|
||||
color: chalk.magenta,
|
||||
icon: '?',
|
||||
tableIcon: '?'
|
||||
},
|
||||
cancelled: {
|
||||
color: chalk.gray,
|
||||
icon: 'x',
|
||||
tableIcon: 'x'
|
||||
},
|
||||
blocked: {
|
||||
color: chalk.red,
|
||||
icon: '!',
|
||||
tableIcon: '!'
|
||||
},
|
||||
completed: {
|
||||
color: chalk.green,
|
||||
icon: '✓',
|
||||
tableIcon: '✓'
|
||||
}
|
||||
};
|
||||
|
||||
const config = statusConfig[status] || {
|
||||
@@ -39,18 +64,7 @@ export function getStatusWithColor(
|
||||
tableIcon: 'X'
|
||||
};
|
||||
|
||||
// Use simple ASCII characters for stable display
|
||||
const simpleIcons = {
|
||||
done: String.fromCharCode(8730), // √
|
||||
pending: 'o',
|
||||
'in-progress': '>',
|
||||
deferred: 'x',
|
||||
blocked: '!',
|
||||
review: '?',
|
||||
cancelled: 'X'
|
||||
};
|
||||
|
||||
const icon = forTable ? simpleIcons[status] || 'X' : config.icon;
|
||||
const icon = forTable ? config.tableIcon : config.icon;
|
||||
return config.color(`${icon} ${status}`);
|
||||
}
|
||||
|
||||
@@ -245,10 +259,24 @@ export function createTaskTable(
|
||||
} = options || {};
|
||||
|
||||
// Calculate dynamic column widths based on terminal width
|
||||
const terminalWidth = process.stdout.columns || 100;
|
||||
const terminalWidth = process.stdout.columns * 0.9 || 100;
|
||||
// Adjust column widths to better match the original layout
|
||||
const baseColWidths = showComplexity
|
||||
? [8, Math.floor(terminalWidth * 0.35), 18, 12, 15, 12] // ID, Title, Status, Priority, Dependencies, Complexity
|
||||
: [8, Math.floor(terminalWidth * 0.4), 18, 12, 20]; // ID, Title, Status, Priority, Dependencies
|
||||
? [
|
||||
Math.floor(terminalWidth * 0.06),
|
||||
Math.floor(terminalWidth * 0.4),
|
||||
Math.floor(terminalWidth * 0.15),
|
||||
Math.floor(terminalWidth * 0.12),
|
||||
Math.floor(terminalWidth * 0.2),
|
||||
Math.floor(terminalWidth * 0.12)
|
||||
] // ID, Title, Status, Priority, Dependencies, Complexity
|
||||
: [
|
||||
Math.floor(terminalWidth * 0.08),
|
||||
Math.floor(terminalWidth * 0.4),
|
||||
Math.floor(terminalWidth * 0.18),
|
||||
Math.floor(terminalWidth * 0.12),
|
||||
Math.floor(terminalWidth * 0.2)
|
||||
]; // ID, Title, Status, Priority, Dependencies
|
||||
|
||||
const headers = [
|
||||
chalk.blue.bold('ID'),
|
||||
@@ -284,11 +312,19 @@ export function createTaskTable(
|
||||
];
|
||||
|
||||
if (showDependencies) {
|
||||
row.push(formatDependenciesWithStatus(task.dependencies, tasks));
|
||||
// For table display, show simple format without status icons
|
||||
if (!task.dependencies || task.dependencies.length === 0) {
|
||||
row.push(chalk.gray('None'));
|
||||
} else {
|
||||
row.push(
|
||||
chalk.cyan(task.dependencies.map((d) => String(d)).join(', '))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (showComplexity && 'complexity' in task) {
|
||||
row.push(getComplexityWithColor(task.complexity as number | string));
|
||||
if (showComplexity) {
|
||||
// Show N/A if no complexity score
|
||||
row.push(chalk.gray('N/A'));
|
||||
}
|
||||
|
||||
table.push(row);
|
||||
|
||||
@@ -1,27 +1,36 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"module": "NodeNext",
|
||||
"lib": ["ES2022"],
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"baseUrl": ".",
|
||||
"rootDir": "./src",
|
||||
"resolveJsonModule": true,
|
||||
"allowJs": false,
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"noImplicitThis": true,
|
||||
"alwaysStrict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"types": ["node"]
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"moduleResolution": "NodeNext",
|
||||
"moduleDetection": "force",
|
||||
"types": ["node"],
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"allowImportingTsExtensions": false
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "tests"]
|
||||
"exclude": ["node_modules", "dist", "tests", "**/*.test.ts", "**/*.spec.ts"]
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# docs
|
||||
|
||||
## 0.0.2
|
||||
|
||||
## 0.0.1
|
||||
|
||||
@@ -128,6 +128,23 @@ You can reorganize tasks in various ways:
|
||||
- Moving a task to a new ID position: `--from=5 --to=25` (even if task 25 doesn't exist yet)
|
||||
- Moving multiple tasks at once: `--from=10,11,12 --to=16,17,18` (must have same number of IDs, Taskmaster will look through each position)
|
||||
|
||||
#### Cross-Tag Move Safety (v0.26.0+)
|
||||
|
||||
For moves between different tags, use the enhanced dependency handling:
|
||||
|
||||
```bash
|
||||
# Move with all dependencies (safest)
|
||||
task-master move --from=5 --to=3 --with-dependencies --tag-from=feature --tag-to=main
|
||||
|
||||
# Move ignoring dependencies (shows safety tips)
|
||||
task-master move --from=5 --to=3 --ignore-dependencies --tag-from=feature --tag-to=main
|
||||
```
|
||||
|
||||
**Enhanced Safety Features:**
|
||||
- **Next Steps Guidance**: After cross-tag moves with `--ignore-dependencies`, the CLI provides "Next Steps" tips with validation and fix guidance
|
||||
- **ID Collision Help**: Dedicated help blocks appear when destination tags already have the target ID, with structured suggestions
|
||||
- **Better Error Messages**: Task collision errors now include actionable suggestions for resolution
|
||||
|
||||
When moving tasks to new IDs:
|
||||
|
||||
- The system automatically creates placeholder tasks for non-existent destination IDs
|
||||
|
||||
@@ -83,9 +83,40 @@ Taskmaster uses two primary methods for configuration:
|
||||
- `VERTEX_PROJECT_ID`: Your Google Cloud project ID for Vertex AI. Required when using the 'vertex' provider.
|
||||
- `VERTEX_LOCATION`: Google Cloud region for Vertex AI (e.g., 'us-central1'). Default is 'us-central1'.
|
||||
- `GOOGLE_APPLICATION_CREDENTIALS`: Path to service account credentials JSON file for Google Cloud auth (alternative to API key for Vertex AI).
|
||||
- `TASKMASTER_ENABLE_CODEBASE_ANALYSIS`: Control codebase analysis features (Claude Code and Gemini CLI integration). Set to 'false' to disable. Default: enabled.
|
||||
|
||||
**Important:** Settings like model ID selections (`main`, `research`, `fallback`), `maxTokens`, `temperature`, `logLevel`, `defaultSubtasks`, `defaultPriority`, and `projectName` are **managed in `.taskmaster/config.json`** (or `.taskmasterconfig` for unmigrated projects), not environment variables.
|
||||
|
||||
## Codebase Analysis Configuration
|
||||
|
||||
Task Master includes advanced codebase analysis features that enhance task generation by analyzing your project structure, existing implementations, and patterns. This is particularly useful when integrated with Claude Code or using Gemini CLI provider.
|
||||
|
||||
### Configuration Options
|
||||
|
||||
**Environment Variable Control (Priority Order: `.env` > MCP session env > `.taskmaster/config.json`)**
|
||||
|
||||
```bash
|
||||
# In .env file
|
||||
TASKMASTER_ENABLE_CODEBASE_ANALYSIS=false # Disable codebase analysis features
|
||||
```
|
||||
|
||||
When enabled, the following commands benefit from codebase analysis:
|
||||
|
||||
- `parse-prd`: Generates tasks informed by project structure
|
||||
- `analyze-complexity`: Considers existing codebase complexity
|
||||
- `add-task`: Creates tasks aligned with project patterns
|
||||
- `update-task`: Updates tasks with relevant codebase context
|
||||
- `update-subtask`: Enhances subtasks based on implementation details
|
||||
- `expand`: Generates subtasks informed by existing code patterns
|
||||
|
||||
### Integration with Claude Code
|
||||
|
||||
When Task Master detects it's running within Claude Code, it automatically provides codebase context to generate more accurate and relevant task details. This feature can be disabled using the environment variable above.
|
||||
|
||||
### Gemini CLI Provider Enhancement
|
||||
|
||||
The Gemini CLI provider includes automatic codebase analysis when generating or updating tasks, resulting in more contextually accurate outputs aligned with your project's architecture.
|
||||
|
||||
## Tagged Task Lists Configuration (v0.17+)
|
||||
|
||||
Taskmaster includes a tagged task lists system for multi-context task management.
|
||||
@@ -147,6 +178,9 @@ PERPLEXITY_API_KEY=pplx-your-key-here
|
||||
|
||||
# Google Vertex AI Configuration (Required if using 'vertex' provider)
|
||||
# VERTEX_PROJECT_ID=your-gcp-project-id
|
||||
|
||||
# Feature Control
|
||||
# TASKMASTER_ENABLE_CODEBASE_ANALYSIS=false # Disable codebase analysis features
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -206,4 +206,25 @@ sidebarTitle: "CLI Commands"
|
||||
task-master init
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Move Tasks">
|
||||
```bash
|
||||
# Move a task or subtask to a new position
|
||||
task-master move --from=<id> --to=<id>
|
||||
|
||||
# Move multiple tasks at once (same number of IDs required)
|
||||
task-master move --from=1,2,3 --to=7,8,9
|
||||
|
||||
# Cross-tag moves with dependency handling
|
||||
task-master move --from=5 --to=3 --with-dependencies --tag-from=feature --tag-to=main
|
||||
task-master move --from=5 --to=3 --ignore-dependencies --tag-from=feature --tag-to=main
|
||||
|
||||
# Note: --force flag is deprecated, use --with-dependencies or --ignore-dependencies instead
|
||||
```
|
||||
|
||||
**Cross-tag Move Safety Features:**
|
||||
- CLI shows "Next Steps" guidance after moves that ignore dependencies
|
||||
- Dedicated help is displayed for ID collisions with structured suggestions
|
||||
- Better error handling with actionable suggestions for resolution
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "docs",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.2",
|
||||
"private": true,
|
||||
"description": "Task Master documentation powered by Mintlify",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# Change Log
|
||||
|
||||
## 0.24.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`8783708`](https://github.com/eyaltoledano/claude-task-master/commit/8783708e5e3389890a78fcf685d3da0580e73b3f), [`df26c65`](https://github.com/eyaltoledano/claude-task-master/commit/df26c65632000874a73504963b08f18c46283144), [`37af0f1`](https://github.com/eyaltoledano/claude-task-master/commit/37af0f191227a68d119b7f89a377bf932ee3ac66), [`c4f92f6`](https://github.com/eyaltoledano/claude-task-master/commit/c4f92f6a0aee3435c56eb8d27d9aa9204284833e), [`8783708`](https://github.com/eyaltoledano/claude-task-master/commit/8783708e5e3389890a78fcf685d3da0580e73b3f), [`4dad2fd`](https://github.com/eyaltoledano/claude-task-master/commit/4dad2fd613ceac56a65ae9d3c1c03092b8860ac9)]:
|
||||
- task-master-ai@0.26.0
|
||||
|
||||
## 0.24.2-rc.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"private": true,
|
||||
"displayName": "TaskMaster",
|
||||
"description": "A visual Kanban board interface for TaskMaster projects in VS Code",
|
||||
"version": "0.24.2-rc.1",
|
||||
"version": "0.24.2",
|
||||
"publisher": "Hamster",
|
||||
"icon": "assets/icon.png",
|
||||
"engines": {
|
||||
@@ -240,7 +240,7 @@
|
||||
"check-types": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"task-master-ai": "0.26.0-rc.1"
|
||||
"task-master-ai": "0.26.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
|
||||
@@ -105,9 +105,9 @@ export const TaskMetadataSidebar: React.FC<TaskMetadataSidebarProps> = ({
|
||||
if (!currentTask || isStartingTask) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
setIsStartingTask(true);
|
||||
|
||||
|
||||
// Send message to extension to open terminal
|
||||
if (vscode) {
|
||||
vscode.postMessage({
|
||||
@@ -116,7 +116,7 @@ export const TaskMetadataSidebar: React.FC<TaskMetadataSidebarProps> = ({
|
||||
taskTitle: currentTask.title
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Reset loading state after a short delay
|
||||
setTimeout(() => {
|
||||
setIsStartingTask(false);
|
||||
@@ -318,7 +318,13 @@ export const TaskMetadataSidebar: React.FC<TaskMetadataSidebarProps> = ({
|
||||
variant="default"
|
||||
size="sm"
|
||||
className="w-full text-xs"
|
||||
disabled={isRegenerating || isAppending || isStartingTask || currentTask?.status === 'done'}
|
||||
disabled={
|
||||
isRegenerating ||
|
||||
isAppending ||
|
||||
isStartingTask ||
|
||||
currentTask?.status === 'done' ||
|
||||
currentTask?.status === 'in-progress'
|
||||
}
|
||||
>
|
||||
{isStartingTask ? (
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
|
||||
@@ -202,16 +202,16 @@ export const TaskDetailsView: React.FC<TaskDetailsViewProps> = ({
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Right column - Metadata (1/3 width) */}
|
||||
<TaskMetadataSidebar
|
||||
currentTask={currentTask}
|
||||
tasks={allTasks}
|
||||
complexity={complexity}
|
||||
isSubtask={isSubtask}
|
||||
sendMessage={sendMessage}
|
||||
onStatusChange={handleStatusChange}
|
||||
onDependencyClick={handleDependencyClick}
|
||||
/>
|
||||
{/* Right column - Metadata (1/3 width) */}
|
||||
<TaskMetadataSidebar
|
||||
currentTask={currentTask}
|
||||
tasks={allTasks}
|
||||
complexity={complexity}
|
||||
isSubtask={isSubtask}
|
||||
sendMessage={sendMessage}
|
||||
onStatusChange={handleStatusChange}
|
||||
onDependencyClick={handleDependencyClick}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -363,20 +363,25 @@ export class WebviewManager {
|
||||
|
||||
case 'openTerminal':
|
||||
// Open VS Code terminal for task execution
|
||||
this.logger.info(`Opening terminal for task ${data.taskId}: ${data.taskTitle}`);
|
||||
|
||||
this.logger.log(
|
||||
`Opening terminal for task ${data.taskId}: ${data.taskTitle}`
|
||||
);
|
||||
|
||||
try {
|
||||
const terminal = vscode.window.createTerminal({
|
||||
name: `Task ${data.taskId}: ${data.taskTitle}`,
|
||||
cwd: this.workspaceRoot
|
||||
cwd: vscode.workspace.workspaceFolders?.[0]?.uri.fsPath
|
||||
});
|
||||
terminal.show();
|
||||
|
||||
this.logger.info('Terminal created and shown successfully');
|
||||
|
||||
this.logger.log('Terminal created and shown successfully');
|
||||
response = { success: true };
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to create terminal:', error);
|
||||
response = { success: false, error: error.message };
|
||||
response = {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error'
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -20,357 +20,8 @@
|
||||
* Main entry point for globally installed package
|
||||
*/
|
||||
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, resolve } from 'path';
|
||||
import { createRequire } from 'module';
|
||||
import { spawn } from 'child_process';
|
||||
import { Command } from 'commander';
|
||||
import { displayHelp, displayBanner } from '../scripts/modules/ui.js';
|
||||
import { registerCommands } from '../scripts/modules/commands.js';
|
||||
import { detectCamelCaseFlags } from '../scripts/modules/utils.js';
|
||||
import chalk from 'chalk';
|
||||
// Direct imports instead of spawning child processes
|
||||
import { runCLI } from '../scripts/modules/commands.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
// Get package information
|
||||
const packageJson = require('../package.json');
|
||||
const version = packageJson.version;
|
||||
|
||||
// Get paths to script files
|
||||
const devScriptPath = resolve(__dirname, '../scripts/dev.js');
|
||||
const initScriptPath = resolve(__dirname, '../scripts/init.js');
|
||||
|
||||
// Helper function to run dev.js with arguments
|
||||
function runDevScript(args) {
|
||||
// Debug: Show the transformed arguments when DEBUG=1 is set
|
||||
if (process.env.DEBUG === '1') {
|
||||
console.error('\nDEBUG - CLI Wrapper Analysis:');
|
||||
console.error('- Original command: ' + process.argv.join(' '));
|
||||
console.error('- Transformed args: ' + args.join(' '));
|
||||
console.error(
|
||||
'- dev.js will receive: node ' +
|
||||
devScriptPath +
|
||||
' ' +
|
||||
args.join(' ') +
|
||||
'\n'
|
||||
);
|
||||
}
|
||||
|
||||
// For testing: If TEST_MODE is set, just print args and exit
|
||||
if (process.env.TEST_MODE === '1') {
|
||||
console.log('Would execute:');
|
||||
console.log(`node ${devScriptPath} ${args.join(' ')}`);
|
||||
process.exit(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const child = spawn('node', [devScriptPath, ...args], {
|
||||
stdio: 'inherit',
|
||||
cwd: process.cwd()
|
||||
});
|
||||
|
||||
child.on('close', (code) => {
|
||||
process.exit(code);
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to detect camelCase and convert to kebab-case
|
||||
const toKebabCase = (str) => str.replace(/([A-Z])/g, '-$1').toLowerCase();
|
||||
|
||||
/**
|
||||
* Create a wrapper action that passes the command to dev.js
|
||||
* @param {string} commandName - The name of the command
|
||||
* @returns {Function} Wrapper action function
|
||||
*/
|
||||
function createDevScriptAction(commandName) {
|
||||
return (options, cmd) => {
|
||||
// Check for camelCase flags and error out with helpful message
|
||||
const camelCaseFlags = detectCamelCaseFlags(process.argv);
|
||||
|
||||
// If camelCase flags were found, show error and exit
|
||||
if (camelCaseFlags.length > 0) {
|
||||
console.error('\nError: Please use kebab-case for CLI flags:');
|
||||
camelCaseFlags.forEach((flag) => {
|
||||
console.error(` Instead of: --${flag.original}`);
|
||||
console.error(` Use: --${flag.kebabCase}`);
|
||||
});
|
||||
console.error(
|
||||
'\nExample: task-master parse-prd --num-tasks=5 instead of --numTasks=5\n'
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Since we've ensured no camelCase flags, we can now just:
|
||||
// 1. Start with the command name
|
||||
const args = [commandName];
|
||||
|
||||
// 3. Get positional arguments and explicit flags from the command line
|
||||
const commandArgs = [];
|
||||
const positionals = new Set(); // Track positional args we've seen
|
||||
|
||||
// Find the command in raw process.argv to extract args
|
||||
const commandIndex = process.argv.indexOf(commandName);
|
||||
if (commandIndex !== -1) {
|
||||
// Process all args after the command name
|
||||
for (let i = commandIndex + 1; i < process.argv.length; i++) {
|
||||
const arg = process.argv[i];
|
||||
|
||||
if (arg.startsWith('--')) {
|
||||
// It's a flag - pass through as is
|
||||
commandArgs.push(arg);
|
||||
// Skip the next arg if this is a flag with a value (not --flag=value format)
|
||||
if (
|
||||
!arg.includes('=') &&
|
||||
i + 1 < process.argv.length &&
|
||||
!process.argv[i + 1].startsWith('--')
|
||||
) {
|
||||
commandArgs.push(process.argv[++i]);
|
||||
}
|
||||
} else if (!positionals.has(arg)) {
|
||||
// It's a positional argument we haven't seen
|
||||
commandArgs.push(arg);
|
||||
positionals.add(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add all command line args we collected
|
||||
args.push(...commandArgs);
|
||||
|
||||
// 4. Add default options from Commander if not specified on command line
|
||||
// Track which options we've seen on the command line
|
||||
const userOptions = new Set();
|
||||
for (const arg of commandArgs) {
|
||||
if (arg.startsWith('--')) {
|
||||
// Extract option name (without -- and value)
|
||||
const name = arg.split('=')[0].slice(2);
|
||||
userOptions.add(name);
|
||||
|
||||
// Add the kebab-case version too, to prevent duplicates
|
||||
const kebabName = name.replace(/([A-Z])/g, '-$1').toLowerCase();
|
||||
userOptions.add(kebabName);
|
||||
|
||||
// Add the camelCase version as well
|
||||
const camelName = kebabName.replace(/-([a-z])/g, (_, letter) =>
|
||||
letter.toUpperCase()
|
||||
);
|
||||
userOptions.add(camelName);
|
||||
}
|
||||
}
|
||||
|
||||
// Add Commander-provided defaults for options not specified by user
|
||||
Object.entries(options).forEach(([key, value]) => {
|
||||
// Debug output to see what keys we're getting
|
||||
if (process.env.DEBUG === '1') {
|
||||
console.error(`DEBUG - Processing option: ${key} = ${value}`);
|
||||
}
|
||||
|
||||
// Special case for numTasks > num-tasks (a known problem case)
|
||||
if (key === 'numTasks') {
|
||||
if (process.env.DEBUG === '1') {
|
||||
console.error('DEBUG - Converting numTasks to num-tasks');
|
||||
}
|
||||
if (!userOptions.has('num-tasks') && !userOptions.has('numTasks')) {
|
||||
args.push(`--num-tasks=${value}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip built-in Commander properties and options the user provided
|
||||
if (
|
||||
['parent', 'commands', 'options', 'rawArgs'].includes(key) ||
|
||||
userOptions.has(key)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Also check the kebab-case version of this key
|
||||
const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
|
||||
if (userOptions.has(kebabKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add default values, using kebab-case for the parameter name
|
||||
if (value !== undefined) {
|
||||
if (typeof value === 'boolean') {
|
||||
if (value === true) {
|
||||
args.push(`--${kebabKey}`);
|
||||
} else if (value === false && key === 'generate') {
|
||||
args.push('--skip-generate');
|
||||
}
|
||||
} else {
|
||||
// Always use kebab-case for option names
|
||||
args.push(`--${kebabKey}=${value}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Special handling for parent parameter (uses -p)
|
||||
if (options.parent && !args.includes('-p') && !userOptions.has('parent')) {
|
||||
args.push('-p', options.parent);
|
||||
}
|
||||
|
||||
// Debug output for troubleshooting
|
||||
if (process.env.DEBUG === '1') {
|
||||
console.error('DEBUG - Command args:', commandArgs);
|
||||
console.error('DEBUG - User options:', Array.from(userOptions));
|
||||
console.error('DEBUG - Commander options:', options);
|
||||
console.error('DEBUG - Final args:', args);
|
||||
}
|
||||
|
||||
// Run the script with our processed args
|
||||
runDevScript(args);
|
||||
};
|
||||
}
|
||||
|
||||
// // Special case for the 'init' command which uses a different script
|
||||
// function registerInitCommand(program) {
|
||||
// program
|
||||
// .command('init')
|
||||
// .description('Initialize a new project')
|
||||
// .option('-y, --yes', 'Skip prompts and use default values')
|
||||
// .option('-n, --name <name>', 'Project name')
|
||||
// .option('-d, --description <description>', 'Project description')
|
||||
// .option('-v, --version <version>', 'Project version')
|
||||
// .option('-a, --author <author>', 'Author name')
|
||||
// .option('--skip-install', 'Skip installing dependencies')
|
||||
// .option('--dry-run', 'Show what would be done without making changes')
|
||||
// .action((options) => {
|
||||
// // Pass through any options to the init script
|
||||
// const args = [
|
||||
// '--yes',
|
||||
// 'name',
|
||||
// 'description',
|
||||
// 'version',
|
||||
// 'author',
|
||||
// 'skip-install',
|
||||
// 'dry-run'
|
||||
// ]
|
||||
// .filter((opt) => options[opt])
|
||||
// .map((opt) => {
|
||||
// if (opt === 'yes' || opt === 'skip-install' || opt === 'dry-run') {
|
||||
// return `--${opt}`;
|
||||
// }
|
||||
// return `--${opt}=${options[opt]}`;
|
||||
// });
|
||||
|
||||
// const child = spawn('node', [initScriptPath, ...args], {
|
||||
// stdio: 'inherit',
|
||||
// cwd: process.cwd()
|
||||
// });
|
||||
|
||||
// child.on('close', (code) => {
|
||||
// process.exit(code);
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
// Set up the command-line interface
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.name('task-master')
|
||||
.description('Claude Task Master CLI')
|
||||
.version(version)
|
||||
.addHelpText('afterAll', () => {
|
||||
// Use the same help display function as dev.js for consistency
|
||||
displayHelp();
|
||||
return ''; // Return empty string to prevent commander's default help
|
||||
});
|
||||
|
||||
// Add custom help option to directly call our help display
|
||||
program.helpOption('-h, --help', 'Display help information');
|
||||
program.on('--help', () => {
|
||||
displayHelp();
|
||||
});
|
||||
|
||||
// // Add special case commands
|
||||
// registerInitCommand(program);
|
||||
|
||||
program
|
||||
.command('dev')
|
||||
.description('Run the dev.js script')
|
||||
.action(() => {
|
||||
const args = process.argv.slice(process.argv.indexOf('dev') + 1);
|
||||
runDevScript(args);
|
||||
});
|
||||
|
||||
// Use a temporary Command instance to get all command definitions
|
||||
const tempProgram = new Command();
|
||||
registerCommands(tempProgram);
|
||||
|
||||
// For each command in the temp instance, add a modified version to our actual program
|
||||
tempProgram.commands.forEach((cmd) => {
|
||||
if (['dev'].includes(cmd.name())) {
|
||||
// Skip commands we've already defined specially
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new command with the same name and description
|
||||
const newCmd = program.command(cmd.name()).description(cmd.description());
|
||||
|
||||
// Copy all options
|
||||
cmd.options.forEach((opt) => {
|
||||
newCmd.option(opt.flags, opt.description, opt.defaultValue);
|
||||
});
|
||||
|
||||
// Set the action to proxy to dev.js
|
||||
newCmd.action(createDevScriptAction(cmd.name()));
|
||||
});
|
||||
|
||||
// Parse the command line arguments
|
||||
program.parse(process.argv);
|
||||
|
||||
// Add global error handling for unknown commands and options
|
||||
process.on('uncaughtException', (err) => {
|
||||
// Check if this is a commander.js unknown option error
|
||||
if (err.code === 'commander.unknownOption') {
|
||||
const option = err.message.match(/'([^']+)'/)?.[1];
|
||||
const commandArg = process.argv.find(
|
||||
(arg) =>
|
||||
!arg.startsWith('-') &&
|
||||
arg !== 'task-master' &&
|
||||
!arg.includes('/') &&
|
||||
arg !== 'node'
|
||||
);
|
||||
const command = commandArg || 'unknown';
|
||||
|
||||
console.error(chalk.red(`Error: Unknown option '${option}'`));
|
||||
console.error(
|
||||
chalk.yellow(
|
||||
`Run 'task-master ${command} --help' to see available options for this command`
|
||||
)
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check if this is a commander.js unknown command error
|
||||
if (err.code === 'commander.unknownCommand') {
|
||||
const command = err.message.match(/'([^']+)'/)?.[1];
|
||||
|
||||
console.error(chalk.red(`Error: Unknown command '${command}'`));
|
||||
console.error(
|
||||
chalk.yellow(`Run 'task-master --help' to see available commands`)
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Handle other uncaught exceptions
|
||||
console.error(chalk.red(`Error: ${err.message}`));
|
||||
if (process.env.DEBUG === '1') {
|
||||
console.error(err);
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// Show help if no command was provided (just 'task-master' with no args)
|
||||
if (process.argv.length <= 2) {
|
||||
displayBanner();
|
||||
displayHelp();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Add exports at the end of the file
|
||||
export { detectCamelCaseFlags };
|
||||
// Simply run the CLI directly
|
||||
runCLI();
|
||||
|
||||
@@ -18,7 +18,17 @@ export default {
|
||||
testMatch: ['**/__tests__/**/*.js', '**/?(*.)+(spec|test).js'],
|
||||
|
||||
// Transform files
|
||||
transform: {},
|
||||
preset: 'ts-jest/presets/default-esm',
|
||||
extensionsToTreatAsEsm: ['.ts'],
|
||||
moduleFileExtensions: ['js', 'ts', 'json', 'node'],
|
||||
transform: {
|
||||
'^.+\\.ts$': [
|
||||
'ts-jest',
|
||||
{
|
||||
useESM: true
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// Disable transformations for node_modules
|
||||
transformIgnorePatterns: ['/node_modules/'],
|
||||
@@ -27,6 +37,7 @@ export default {
|
||||
moduleNameMapper: {
|
||||
'^@/(.*)$': '<rootDir>/$1'
|
||||
},
|
||||
resolver: '<rootDir>/jest.resolver.cjs',
|
||||
|
||||
// Setup module aliases
|
||||
moduleDirectories: ['node_modules', '<rootDir>'],
|
||||
|
||||
19
jest.resolver.cjs
Normal file
19
jest.resolver.cjs
Normal file
@@ -0,0 +1,19 @@
|
||||
const { defaultResolver } = require('jest-resolve');
|
||||
module.exports = function customResolver(request, options) {
|
||||
const resolve = options.defaultResolver || defaultResolver;
|
||||
|
||||
try {
|
||||
return resolve(request, options);
|
||||
} catch (error) {
|
||||
if (request.startsWith('.') && request.endsWith('.js')) {
|
||||
try {
|
||||
return resolve(request.replace(/\.js$/, '.ts'), options);
|
||||
} catch (tsError) {
|
||||
tsError.cause = tsError.cause ?? error;
|
||||
throw tsError;
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
73
output.txt
Normal file
73
output.txt
Normal file
File diff suppressed because one or more lines are too long
6927
package-lock.json
generated
6927
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
27
package.json
27
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "task-master-ai",
|
||||
"version": "0.26.0-rc.1",
|
||||
"version": "0.26.0",
|
||||
"description": "A task management system for ambitious AI-driven development that doesn't overwhelm and confuse Cursor.",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
@@ -11,23 +11,12 @@
|
||||
},
|
||||
"workspaces": ["apps/*", "packages/*", "."],
|
||||
"scripts": {
|
||||
"build": "npm run build:build-config && tsup",
|
||||
"dev": "tsup --watch",
|
||||
"build": "npm run build:build-config && cross-env NODE_ENV=production tsdown",
|
||||
"dev": "tsdown --watch='packages/*/src/**/*' --watch='apps/cli/src/**/*' --watch='bin/**/*' --watch='mcp-server/**/*'",
|
||||
"turbo:dev": "turbo dev",
|
||||
"turbo:build": "turbo build",
|
||||
"dev:main": "tsup --watch --onSuccess 'echo \"📦 Main package built\" && npm link'",
|
||||
"dev:legacy": "npm run build:build-config && concurrently -n \"core,cli,main\" -c \"blue,green,yellow\" \"npm run dev:core\" \"npm run dev:cli\" \"npm run dev:main\"",
|
||||
"dev:core": "npm run dev -w @tm/core",
|
||||
"dev:cli": "npm run dev -w @tm/cli",
|
||||
"build:packages": "turbo build --filter='./packages/*' --filter='./apps/*'",
|
||||
"build:packages:parallel": "turbo build --filter='./packages/*' --filter='./apps/*'",
|
||||
"turbo:typecheck": "turbo typecheck",
|
||||
"build:build-config": "npm run build -w @tm/build-config",
|
||||
"build:core": "npm run build -w @tm/core",
|
||||
"build:cli": "npm run build -w @tm/cli",
|
||||
"typecheck": "turbo typecheck",
|
||||
"typecheck:all": "turbo typecheck",
|
||||
"typecheck:core": "npm run typecheck -w @tm/core",
|
||||
"typecheck:cli": "npm run typecheck -w @tm/cli",
|
||||
"test": "node --experimental-vm-modules node_modules/.bin/jest",
|
||||
"test:unit": "node --experimental-vm-modules node_modules/.bin/jest --testPathPattern=unit",
|
||||
"test:integration": "node --experimental-vm-modules node_modules/.bin/jest --testPathPattern=integration",
|
||||
@@ -62,7 +51,6 @@
|
||||
"license": "MIT WITH Commons-Clause",
|
||||
"dependencies": {
|
||||
"@ai-sdk/amazon-bedrock": "^2.2.9",
|
||||
"@tm/cli": "*",
|
||||
"@ai-sdk/anthropic": "^1.2.10",
|
||||
"@ai-sdk/azure": "^1.3.17",
|
||||
"@ai-sdk/google": "^1.2.13",
|
||||
@@ -77,6 +65,7 @@
|
||||
"@inquirer/search": "^3.0.15",
|
||||
"@openrouter/ai-sdk-provider": "^0.4.5",
|
||||
"@streamparser/json": "^0.0.22",
|
||||
"@tm/cli": "*",
|
||||
"ai": "^4.3.10",
|
||||
"ajv": "^8.17.1",
|
||||
"ajv-formats": "^3.0.1",
|
||||
@@ -100,6 +89,8 @@
|
||||
"jsonrepair": "^3.13.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lru-cache": "^10.2.0",
|
||||
"marked": "^15.0.12",
|
||||
"marked-terminal": "^7.3.0",
|
||||
"ollama-ai-provider": "^1.2.0",
|
||||
"openai": "^4.89.0",
|
||||
"ora": "^8.2.0",
|
||||
@@ -134,6 +125,7 @@
|
||||
"@changesets/changelog-github": "^0.5.1",
|
||||
"@changesets/cli": "^2.28.1",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/marked-terminal": "^6.1.1",
|
||||
"concurrently": "^9.2.1",
|
||||
"cross-env": "^10.0.0",
|
||||
"dotenv-mono": "^1.5.1",
|
||||
@@ -143,7 +135,8 @@
|
||||
"mock-fs": "^5.5.0",
|
||||
"prettier": "^3.5.3",
|
||||
"supertest": "^7.1.0",
|
||||
"tsup": "^8.5.0",
|
||||
"ts-jest": "^29.4.2",
|
||||
"tsdown": "^0.15.2",
|
||||
"tsx": "^4.16.2",
|
||||
"turbo": "^2.5.6",
|
||||
"typescript": "^5.9.2"
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
"version": "1.0.0",
|
||||
"description": "Shared build configuration for Task Master monorepo",
|
||||
"type": "module",
|
||||
"main": "./dist/tsup.base.js",
|
||||
"types": "./dist/tsup.base.d.ts",
|
||||
"main": "./dist/tsdown.base.js",
|
||||
"types": "./src/tsdown.base.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/tsup.base.d.ts",
|
||||
"import": "./dist/tsup.base.js"
|
||||
"types": "./src/tsdown.base.ts",
|
||||
"import": "./dist/tsdown.base.js"
|
||||
}
|
||||
},
|
||||
"files": ["dist", "src"],
|
||||
|
||||
46
packages/build-config/src/tsdown.base.ts
Normal file
46
packages/build-config/src/tsdown.base.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Base tsdown configuration for Task Master monorepo
|
||||
* Provides shared configuration that can be extended by individual packages
|
||||
*/
|
||||
import type { UserConfig } from 'tsdown';
|
||||
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
const isDevelopment = !isProduction;
|
||||
|
||||
/**
|
||||
* Environment helpers
|
||||
*/
|
||||
export const env = {
|
||||
isProduction,
|
||||
isDevelopment,
|
||||
NODE_ENV: process.env.NODE_ENV || 'development'
|
||||
};
|
||||
|
||||
/**
|
||||
* Base tsdown configuration for all packages
|
||||
* Since everything gets bundled into root dist/ anyway, use consistent settings
|
||||
*/
|
||||
export const baseConfig: Partial<UserConfig> = {
|
||||
sourcemap: isDevelopment,
|
||||
format: 'esm',
|
||||
platform: 'node',
|
||||
dts: isDevelopment,
|
||||
minify: isProduction,
|
||||
treeshake: isProduction,
|
||||
// Keep all npm dependencies external (available via node_modules)
|
||||
external: [/^[^@./]/, /^@(?!tm\/)/]
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility function to merge configurations
|
||||
* Simplified for tsdown usage
|
||||
*/
|
||||
export function mergeConfig(
|
||||
base: Partial<UserConfig>,
|
||||
overrides: Partial<UserConfig>
|
||||
): Partial<UserConfig> {
|
||||
return {
|
||||
...base,
|
||||
...overrides
|
||||
};
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
/**
|
||||
* Base tsup configuration for Task Master monorepo
|
||||
* Provides shared configuration that can be extended by individual packages
|
||||
*/
|
||||
import type { Options } from 'tsup';
|
||||
import * as dotenv from 'dotenv-mono';
|
||||
|
||||
dotenv.load();
|
||||
|
||||
console.log(
|
||||
'TM_PUBLIC_BASE_DOMAIN:',
|
||||
process.env.TM_PUBLIC_BASE_DOMAIN,
|
||||
'TM_PUBLIC_SUPABASE_URL:',
|
||||
process.env.TM_PUBLIC_SUPABASE_URL,
|
||||
'TM_PUBLIC_SUPABASE_ANON_KEY:',
|
||||
process.env.TM_PUBLIC_SUPABASE_ANON_KEY
|
||||
);
|
||||
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
const isDevelopment = !isProduction;
|
||||
|
||||
const envVariables = {
|
||||
TM_PUBLIC_BASE_DOMAIN: process.env.TM_PUBLIC_BASE_DOMAIN ?? '',
|
||||
TM_PUBLIC_SUPABASE_URL: process.env.TM_PUBLIC_SUPABASE_URL ?? '',
|
||||
TM_PUBLIC_SUPABASE_ANON_KEY: process.env.TM_PUBLIC_SUPABASE_ANON_KEY ?? ''
|
||||
};
|
||||
|
||||
console.log('envVariables:', envVariables);
|
||||
|
||||
/**
|
||||
* Environment helpers
|
||||
*/
|
||||
export const env = {
|
||||
isProduction,
|
||||
isDevelopment,
|
||||
NODE_ENV: process.env.NODE_ENV || 'development',
|
||||
...envVariables
|
||||
};
|
||||
|
||||
/**
|
||||
* Base tsup configuration for all packages
|
||||
* Since everything gets bundled into root dist/ anyway, use consistent settings
|
||||
*/
|
||||
export const baseConfig: Partial<Options> = {
|
||||
format: ['esm'],
|
||||
target: 'node18',
|
||||
sourcemap: isDevelopment,
|
||||
clean: true,
|
||||
dts: false,
|
||||
minify: isProduction,
|
||||
treeshake: isProduction,
|
||||
splitting: false,
|
||||
// Don't bundle any other dependencies (auto-external all node_modules)
|
||||
external: [/^[^./]/],
|
||||
env: envVariables,
|
||||
esbuildOptions(options) {
|
||||
options.platform = 'node';
|
||||
// Allow importing TypeScript from JavaScript
|
||||
options.resolveExtensions = ['.ts', '.js', '.mjs', '.json'];
|
||||
// Better source mapping in development only
|
||||
options.sourcesContent = isDevelopment;
|
||||
// Keep original names for better debugging in development
|
||||
options.keepNames = isDevelopment;
|
||||
},
|
||||
// Watch mode configuration for development
|
||||
watch: false
|
||||
};
|
||||
|
||||
/**
|
||||
* Legacy external modules list - kept for backwards compatibility
|
||||
* Note: When using tsup-node, this is not needed as it automatically
|
||||
* excludes dependencies and peerDependencies from package.json
|
||||
*/
|
||||
export const commonExternals = [
|
||||
// Native Node.js modules (for cases where tsup is used instead of tsup-node)
|
||||
'fs',
|
||||
'path',
|
||||
'child_process',
|
||||
'crypto',
|
||||
'os',
|
||||
'url',
|
||||
'util',
|
||||
'stream',
|
||||
'http',
|
||||
'https',
|
||||
'events',
|
||||
'assert',
|
||||
'buffer',
|
||||
'querystring',
|
||||
'readline',
|
||||
'zlib',
|
||||
'tty',
|
||||
'net',
|
||||
'dgram',
|
||||
'dns',
|
||||
'tls',
|
||||
'cluster',
|
||||
'process',
|
||||
'module'
|
||||
];
|
||||
|
||||
/**
|
||||
* Utility function to merge configurations
|
||||
* Simplified for tsup-node usage
|
||||
*/
|
||||
export function mergeConfig(
|
||||
baseConfig: Partial<Options>,
|
||||
overrides: Partial<Options>
|
||||
): Options {
|
||||
return {
|
||||
...baseConfig,
|
||||
...overrides,
|
||||
// Merge esbuildOptions
|
||||
esbuildOptions(options, context) {
|
||||
if (baseConfig.esbuildOptions) {
|
||||
baseConfig.esbuildOptions(options, context);
|
||||
}
|
||||
if (overrides.esbuildOptions) {
|
||||
overrides.esbuildOptions(options, context);
|
||||
}
|
||||
}
|
||||
} as Options;
|
||||
}
|
||||
@@ -7,54 +7,19 @@
|
||||
"types": "./src/index.ts",
|
||||
"main": "./dist/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"import": "./dist/index.js"
|
||||
},
|
||||
"./auth": {
|
||||
"types": "./src/auth/index.ts",
|
||||
"import": "./dist/auth/index.js"
|
||||
},
|
||||
"./storage": {
|
||||
"types": "./src/storage/index.ts",
|
||||
"import": "./dist/storage/index.js"
|
||||
},
|
||||
"./config": {
|
||||
"types": "./src/config/index.ts",
|
||||
"import": "./dist/config/index.js"
|
||||
},
|
||||
"./providers": {
|
||||
"types": "./src/providers/index.ts",
|
||||
"import": "./dist/providers/index.js"
|
||||
},
|
||||
"./services": {
|
||||
"types": "./src/services/index.ts",
|
||||
"import": "./dist/services/index.js"
|
||||
},
|
||||
"./errors": {
|
||||
"types": "./src/errors/index.ts",
|
||||
"import": "./dist/errors/index.js"
|
||||
},
|
||||
"./logger": {
|
||||
"types": "./src/logger/index.ts",
|
||||
"import": "./dist/logger/index.js"
|
||||
},
|
||||
"./types": {
|
||||
"types": "./src/types/index.ts",
|
||||
"import": "./dist/types/index.js"
|
||||
},
|
||||
"./interfaces": {
|
||||
"types": "./src/interfaces/index.ts",
|
||||
"import": "./dist/interfaces/index.js"
|
||||
},
|
||||
"./utils": {
|
||||
"types": "./src/utils/index.ts",
|
||||
"import": "./dist/utils/index.js"
|
||||
}
|
||||
".": "./src/index.ts",
|
||||
"./auth": "./src/auth/index.ts",
|
||||
"./storage": "./src/storage/index.ts",
|
||||
"./config": "./src/config/index.ts",
|
||||
"./providers": "./src/providers/index.ts",
|
||||
"./services": "./src/services/index.ts",
|
||||
"./errors": "./src/errors/index.ts",
|
||||
"./logger": "./src/logger/index.ts",
|
||||
"./types": "./src/types/index.ts",
|
||||
"./interfaces": "./src/interfaces/index.ts",
|
||||
"./utils": "./src/utils/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"test:coverage": "vitest run --coverage",
|
||||
@@ -71,9 +36,12 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.9.4",
|
||||
"@tm/build-config": "*",
|
||||
"@types/node": "^20.11.30",
|
||||
"@vitest/coverage-v8": "^2.0.5",
|
||||
"dotenv-mono": "^1.3.14",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsup": "^8.5.0",
|
||||
"typescript": "^5.4.3",
|
||||
"vitest": "^2.0.5"
|
||||
},
|
||||
|
||||
@@ -53,6 +53,7 @@ export type OutputFormat = (typeof OUTPUT_FORMATS)[number];
|
||||
*/
|
||||
export const STATUS_ICONS: Record<TaskStatus, string> = {
|
||||
done: '✓',
|
||||
completed: '✓',
|
||||
'in-progress': '►',
|
||||
blocked: '⭕',
|
||||
pending: '○',
|
||||
@@ -71,5 +72,6 @@ export const STATUS_COLORS: Record<TaskStatus, string> = {
|
||||
deferred: 'gray',
|
||||
cancelled: 'red',
|
||||
blocked: 'magenta',
|
||||
review: 'cyan'
|
||||
review: 'cyan',
|
||||
completed: 'green'
|
||||
} as const;
|
||||
|
||||
@@ -24,7 +24,8 @@ export type TaskStatus =
|
||||
| 'deferred'
|
||||
| 'cancelled'
|
||||
| 'blocked'
|
||||
| 'review';
|
||||
| 'review'
|
||||
| 'completed';
|
||||
|
||||
/**
|
||||
* Task priority levels
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { log } from '../../scripts/modules/utils.js';
|
||||
import packageJson from '../../package.json' with { type: 'json' };
|
||||
|
||||
/**
|
||||
* Reads the version from the nearest package.json relative to this file.
|
||||
@@ -9,27 +6,5 @@ import { log } from '../../scripts/modules/utils.js';
|
||||
* @returns {string} The version string or 'unknown'.
|
||||
*/
|
||||
export function getTaskMasterVersion() {
|
||||
let version = 'unknown';
|
||||
try {
|
||||
// Get the directory of the current module (getPackageVersion.js)
|
||||
const currentModuleFilename = fileURLToPath(import.meta.url);
|
||||
const currentModuleDirname = path.dirname(currentModuleFilename);
|
||||
// Construct the path to package.json relative to this file (../../package.json)
|
||||
const packageJsonPath = path.join(
|
||||
currentModuleDirname,
|
||||
'..',
|
||||
'..',
|
||||
'package.json'
|
||||
);
|
||||
|
||||
if (fs.existsSync(packageJsonPath)) {
|
||||
const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8');
|
||||
const packageJson = JSON.parse(packageJsonContent);
|
||||
version = packageJson.version;
|
||||
}
|
||||
} catch (error) {
|
||||
// Silently fall back to default version
|
||||
log('warn', 'Could not read own package.json for version info.', error);
|
||||
}
|
||||
return version;
|
||||
return packageJson.version || 'unknown';
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ describe('Complex Cross-Tag Scenarios', () => {
|
||||
'..',
|
||||
'..',
|
||||
'..',
|
||||
'bin',
|
||||
'dist',
|
||||
'task-master.js'
|
||||
);
|
||||
|
||||
|
||||
@@ -12,7 +12,15 @@
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true,
|
||||
"baseUrl": "."
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@tm/core": ["./packages/tm-core/src/index.ts"],
|
||||
"@tm/core/*": ["./packages/tm-core/src/*"],
|
||||
"@tm/cli": ["./apps/cli/src/index.ts"],
|
||||
"@tm/cli/*": ["./apps/cli/src/*"],
|
||||
"@tm/build-config": ["./packages/build-config/src/index.ts"],
|
||||
"@tm/build-config/*": ["./packages/build-config/src/*"]
|
||||
}
|
||||
},
|
||||
"tsx": {
|
||||
"tsconfig": {
|
||||
|
||||
31
tsdown.config.ts
Normal file
31
tsdown.config.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { defineConfig } from 'tsdown';
|
||||
import { baseConfig, mergeConfig } from '@tm/build-config';
|
||||
import { load as dotenvLoad } from 'dotenv-mono';
|
||||
|
||||
dotenvLoad();
|
||||
|
||||
// Get all TM_PUBLIC_* env variables for build-time injection
|
||||
const getBuildTimeEnvs = () => {
|
||||
const envs: Record<string, string> = {};
|
||||
for (const [key, value] of Object.entries(process.env)) {
|
||||
if (key.startsWith('TM_PUBLIC_')) {
|
||||
// Return the actual value, not JSON.stringify'd
|
||||
envs[key] = value || '';
|
||||
}
|
||||
}
|
||||
return envs;
|
||||
};
|
||||
|
||||
export default defineConfig(
|
||||
mergeConfig(baseConfig, {
|
||||
entry: {
|
||||
'task-master': 'scripts/dev.js',
|
||||
'mcp-server': 'mcp-server/server.js'
|
||||
},
|
||||
outDir: 'dist',
|
||||
copy: ['public'],
|
||||
// Bundle only our workspace packages, keep npm dependencies external
|
||||
noExternal: [/^@tm\//],
|
||||
env: getBuildTimeEnvs()
|
||||
})
|
||||
);
|
||||
@@ -1,26 +0,0 @@
|
||||
import { defineConfig } from 'tsup';
|
||||
import { baseConfig, mergeConfig } from '@tm/build-config';
|
||||
|
||||
export default defineConfig(
|
||||
mergeConfig(baseConfig, {
|
||||
entry: {
|
||||
'task-master': 'bin/task-master.js',
|
||||
'mcp-server': 'mcp-server/server.js'
|
||||
},
|
||||
outDir: 'dist',
|
||||
publicDir: 'public',
|
||||
// Bundle our monorepo packages but keep node_modules external
|
||||
noExternal: [/@tm\/.*/],
|
||||
// Ensure no code splitting
|
||||
splitting: false,
|
||||
// Better watch configuration
|
||||
ignoreWatch: [
|
||||
'dist',
|
||||
'node_modules',
|
||||
'.git',
|
||||
'tests',
|
||||
'*.test.*',
|
||||
'*.spec.*'
|
||||
]
|
||||
})
|
||||
);
|
||||
@@ -10,7 +10,6 @@
|
||||
"dev": {
|
||||
"cache": false,
|
||||
"persistent": true,
|
||||
"dependsOn": ["^build"],
|
||||
"inputs": [
|
||||
"$TURBO_DEFAULT$",
|
||||
"!{packages,apps}/**/dist/**",
|
||||
|
||||
Reference in New Issue
Block a user