Compare commits
4 Commits
task-maste
...
task-maste
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
255b9f0334 | ||
|
|
cb2c266b2d | ||
|
|
170d6f2f65 | ||
|
|
137ef36278 |
@@ -6,13 +6,14 @@
|
|||||||
"repo": "eyaltoledano/claude-task-master"
|
"repo": "eyaltoledano/claude-task-master"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"commit": true,
|
"commit": false,
|
||||||
"fixed": [],
|
"fixed": [],
|
||||||
"linked": [],
|
|
||||||
"access": "public",
|
"access": "public",
|
||||||
"baseBranch": "main",
|
"baseBranch": "main",
|
||||||
"updateInternalDependencies": "patch",
|
|
||||||
"ignore": [
|
"ignore": [
|
||||||
"docs"
|
"docs",
|
||||||
|
"@tm/cli",
|
||||||
|
"@tm/core",
|
||||||
|
"@tm/build-config"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
5
.changeset/easy-deer-heal.md
Normal file
5
.changeset/easy-deer-heal.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"task-master-ai": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Testing one more pre-release iteration
|
||||||
5
.changeset/moody-oranges-slide.md
Normal file
5
.changeset/moody-oranges-slide.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"task-master-ai": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Test out the RC
|
||||||
5
.changeset/odd-otters-tan.md
Normal file
5
.changeset/odd-otters-tan.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@tm/cli": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
testing this stuff out to see how the release candidate works with monorepo
|
||||||
17
.changeset/pre.json
Normal file
17
.changeset/pre.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"mode": "pre",
|
||||||
|
"tag": "rc",
|
||||||
|
"initialVersions": {
|
||||||
|
"task-master-ai": "0.26.0",
|
||||||
|
"@tm/cli": "0.26.0",
|
||||||
|
"docs": "0.0.2",
|
||||||
|
"extension": "0.24.2",
|
||||||
|
"@tm/build-config": "1.0.0",
|
||||||
|
"@tm/core": "0.26.0"
|
||||||
|
},
|
||||||
|
"changesets": [
|
||||||
|
"moody-oranges-slide",
|
||||||
|
"odd-otters-tan",
|
||||||
|
"wild-ears-look"
|
||||||
|
]
|
||||||
|
}
|
||||||
11
.github/workflows/pre-release.yml
vendored
11
.github/workflows/pre-release.yml
vendored
@@ -65,6 +65,17 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|
||||||
|
- name: Run format
|
||||||
|
run: npm run format
|
||||||
|
env:
|
||||||
|
FORCE_COLOR: 1
|
||||||
|
|
||||||
|
- name: Build packages
|
||||||
|
run: npm run turbo:build
|
||||||
|
env:
|
||||||
|
NODE_ENV: production
|
||||||
|
FORCE_COLOR: 1
|
||||||
|
|
||||||
- name: Create Release Candidate Pull Request or Publish Release Candidate to npm
|
- name: Create Release Candidate Pull Request or Publish Release Candidate to npm
|
||||||
uses: changesets/action@v1
|
uses: changesets/action@v1
|
||||||
with:
|
with:
|
||||||
|
|||||||
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@@ -41,6 +41,12 @@ jobs:
|
|||||||
- name: Check pre-release mode
|
- name: Check pre-release mode
|
||||||
run: node ./.github/scripts/check-pre-release-mode.mjs "main"
|
run: node ./.github/scripts/check-pre-release-mode.mjs "main"
|
||||||
|
|
||||||
|
- name: Build packages
|
||||||
|
run: npm run turbo:build
|
||||||
|
env:
|
||||||
|
NODE_ENV: production
|
||||||
|
FORCE_COLOR: 1
|
||||||
|
|
||||||
- name: Create Release Pull Request or Publish to npm
|
- name: Create Release Pull Request or Publish to npm
|
||||||
uses: changesets/action@v1
|
uses: changesets/action@v1
|
||||||
with:
|
with:
|
||||||
|
|||||||
11
CHANGELOG.md
11
CHANGELOG.md
@@ -1,5 +1,16 @@
|
|||||||
# task-master-ai
|
# task-master-ai
|
||||||
|
|
||||||
|
## 0.27.0-rc.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- [#1213](https://github.com/eyaltoledano/claude-task-master/pull/1213) [`137ef36`](https://github.com/eyaltoledano/claude-task-master/commit/137ef362789a9cdfdb1925e35e0438c1fa6c69ee) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Test out the RC
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [[`137ef36`](https://github.com/eyaltoledano/claude-task-master/commit/137ef362789a9cdfdb1925e35e0438c1fa6c69ee)]:
|
||||||
|
- @tm/cli@0.27.0-rc.0
|
||||||
|
|
||||||
## 0.26.0
|
## 0.26.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
13
apps/cli/CHANGELOG.md
Normal file
13
apps/cli/CHANGELOG.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# @tm/cli
|
||||||
|
|
||||||
|
## 0.27.0-rc.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- [#1213](https://github.com/eyaltoledano/claude-task-master/pull/1213) [`137ef36`](https://github.com/eyaltoledano/claude-task-master/commit/137ef362789a9cdfdb1925e35e0438c1fa6c69ee) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - testing this stuff out to see how the release candidate works with monorepo
|
||||||
|
|
||||||
|
## 1.1.0-rc.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- [#1213](https://github.com/eyaltoledano/claude-task-master/pull/1213) [`cd90b4d`](https://github.com/eyaltoledano/claude-task-master/commit/cd90b4d65fc2f04bdad9fb73aba320b58a124240) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - testing this stuff out to see how the release candidate works with monorepo
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "@tm/cli",
|
"name": "@tm/cli",
|
||||||
"version": "1.0.0",
|
"version": "0.27.0-rc.0",
|
||||||
"description": "Task Master CLI - Command line interface for task management",
|
"description": "Task Master CLI - Command line interface for task management",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./src/index.ts",
|
"types": "./src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -326,7 +326,7 @@ export class ContextCommand extends Command {
|
|||||||
choices: [
|
choices: [
|
||||||
{ name: '(No brief - organization level)', value: null },
|
{ name: '(No brief - organization level)', value: null },
|
||||||
...briefs.map((brief) => ({
|
...briefs.map((brief) => ({
|
||||||
name: `Brief ${brief.id.slice(0, 8)} (${new Date(brief.createdAt).toLocaleDateString()})`,
|
name: `Brief ${brief.id} (${new Date(brief.createdAt).toLocaleDateString()})`,
|
||||||
value: brief
|
value: brief
|
||||||
}))
|
}))
|
||||||
]
|
]
|
||||||
|
|||||||
318
apps/cli/src/commands/set-status.command.ts
Normal file
318
apps/cli/src/commands/set-status.command.ts
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview SetStatusCommand using Commander's native class pattern
|
||||||
|
* Extends Commander.Command for better integration with the framework
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Command } from 'commander';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import boxen from 'boxen';
|
||||||
|
import {
|
||||||
|
createTaskMasterCore,
|
||||||
|
type TaskMasterCore,
|
||||||
|
type TaskStatus
|
||||||
|
} from '@tm/core';
|
||||||
|
import type { StorageType } from '@tm/core/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valid task status values for validation
|
||||||
|
*/
|
||||||
|
const VALID_TASK_STATUSES: TaskStatus[] = [
|
||||||
|
'pending',
|
||||||
|
'in-progress',
|
||||||
|
'done',
|
||||||
|
'deferred',
|
||||||
|
'cancelled',
|
||||||
|
'blocked',
|
||||||
|
'review'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options interface for the set-status command
|
||||||
|
*/
|
||||||
|
export interface SetStatusCommandOptions {
|
||||||
|
id?: string;
|
||||||
|
status?: TaskStatus;
|
||||||
|
format?: 'text' | 'json';
|
||||||
|
silent?: boolean;
|
||||||
|
project?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result type from set-status command
|
||||||
|
*/
|
||||||
|
export interface SetStatusResult {
|
||||||
|
success: boolean;
|
||||||
|
updatedTasks: Array<{
|
||||||
|
taskId: string;
|
||||||
|
oldStatus: TaskStatus;
|
||||||
|
newStatus: TaskStatus;
|
||||||
|
}>;
|
||||||
|
storageType: Exclude<StorageType, 'auto'>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SetStatusCommand extending Commander's Command class
|
||||||
|
* This is a thin presentation layer over @tm/core
|
||||||
|
*/
|
||||||
|
export class SetStatusCommand extends Command {
|
||||||
|
private tmCore?: TaskMasterCore;
|
||||||
|
private lastResult?: SetStatusResult;
|
||||||
|
|
||||||
|
constructor(name?: string) {
|
||||||
|
super(name || 'set-status');
|
||||||
|
|
||||||
|
// Configure the command
|
||||||
|
this.description('Update the status of one or more tasks')
|
||||||
|
.requiredOption(
|
||||||
|
'-i, --id <id>',
|
||||||
|
'Task ID(s) to update (comma-separated for multiple, supports subtasks like 5.2)'
|
||||||
|
)
|
||||||
|
.requiredOption(
|
||||||
|
'-s, --status <status>',
|
||||||
|
`New status (${VALID_TASK_STATUSES.join(', ')})`
|
||||||
|
)
|
||||||
|
.option('-f, --format <format>', 'Output format (text, json)', 'text')
|
||||||
|
.option('--silent', 'Suppress output (useful for programmatic usage)')
|
||||||
|
.option('-p, --project <path>', 'Project root directory', process.cwd())
|
||||||
|
.action(async (options: SetStatusCommandOptions) => {
|
||||||
|
await this.executeCommand(options);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the set-status command
|
||||||
|
*/
|
||||||
|
private async executeCommand(
|
||||||
|
options: SetStatusCommandOptions
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
// Validate required options
|
||||||
|
if (!options.id) {
|
||||||
|
console.error(chalk.red('Error: Task ID is required. Use -i or --id'));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.status) {
|
||||||
|
console.error(
|
||||||
|
chalk.red('Error: Status is required. Use -s or --status')
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate status
|
||||||
|
if (!VALID_TASK_STATUSES.includes(options.status)) {
|
||||||
|
console.error(
|
||||||
|
chalk.red(
|
||||||
|
`Error: Invalid status "${options.status}". Valid options: ${VALID_TASK_STATUSES.join(', ')}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize TaskMaster core
|
||||||
|
this.tmCore = await createTaskMasterCore({
|
||||||
|
projectPath: options.project || process.cwd()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Parse task IDs (handle comma-separated values)
|
||||||
|
const taskIds = options.id.split(',').map((id) => id.trim());
|
||||||
|
|
||||||
|
// Update each task
|
||||||
|
const updatedTasks: Array<{
|
||||||
|
taskId: string;
|
||||||
|
oldStatus: TaskStatus;
|
||||||
|
newStatus: TaskStatus;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
for (const taskId of taskIds) {
|
||||||
|
try {
|
||||||
|
const result = await this.tmCore.updateTaskStatus(
|
||||||
|
taskId,
|
||||||
|
options.status
|
||||||
|
);
|
||||||
|
updatedTasks.push({
|
||||||
|
taskId: result.taskId,
|
||||||
|
oldStatus: result.oldStatus,
|
||||||
|
newStatus: result.newStatus
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage =
|
||||||
|
error instanceof Error ? error.message : String(error);
|
||||||
|
|
||||||
|
if (!options.silent) {
|
||||||
|
console.error(
|
||||||
|
chalk.red(`Failed to update task ${taskId}: ${errorMessage}`)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (options.format === 'json') {
|
||||||
|
console.log(
|
||||||
|
JSON.stringify({
|
||||||
|
success: false,
|
||||||
|
error: errorMessage,
|
||||||
|
taskId,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store result for potential reuse
|
||||||
|
this.lastResult = {
|
||||||
|
success: true,
|
||||||
|
updatedTasks,
|
||||||
|
storageType: this.tmCore.getStorageType() as Exclude<
|
||||||
|
StorageType,
|
||||||
|
'auto'
|
||||||
|
>
|
||||||
|
};
|
||||||
|
|
||||||
|
// Display results
|
||||||
|
this.displayResults(this.lastResult, options);
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage =
|
||||||
|
error instanceof Error ? error.message : 'Unknown error occurred';
|
||||||
|
|
||||||
|
if (!options.silent) {
|
||||||
|
console.error(chalk.red(`Error: ${errorMessage}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.format === 'json') {
|
||||||
|
console.log(JSON.stringify({ success: false, error: errorMessage }));
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exit(1);
|
||||||
|
} finally {
|
||||||
|
// Clean up resources
|
||||||
|
if (this.tmCore) {
|
||||||
|
await this.tmCore.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display results based on format
|
||||||
|
*/
|
||||||
|
private displayResults(
|
||||||
|
result: SetStatusResult,
|
||||||
|
options: SetStatusCommandOptions
|
||||||
|
): void {
|
||||||
|
const format = options.format || 'text';
|
||||||
|
|
||||||
|
switch (format) {
|
||||||
|
case 'json':
|
||||||
|
console.log(JSON.stringify(result, null, 2));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'text':
|
||||||
|
default:
|
||||||
|
if (!options.silent) {
|
||||||
|
this.displayTextResults(result);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display results in text format
|
||||||
|
*/
|
||||||
|
private displayTextResults(result: SetStatusResult): void {
|
||||||
|
if (result.updatedTasks.length === 1) {
|
||||||
|
// Single task update
|
||||||
|
const update = result.updatedTasks[0];
|
||||||
|
console.log(
|
||||||
|
boxen(
|
||||||
|
chalk.white.bold(`✅ Successfully updated task ${update.taskId}`) +
|
||||||
|
'\n\n' +
|
||||||
|
`${chalk.blue('From:')} ${this.getStatusDisplay(update.oldStatus)}\n` +
|
||||||
|
`${chalk.blue('To:')} ${this.getStatusDisplay(update.newStatus)}`,
|
||||||
|
{
|
||||||
|
padding: 1,
|
||||||
|
borderColor: 'green',
|
||||||
|
borderStyle: 'round',
|
||||||
|
margin: { top: 1 }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Multiple task updates
|
||||||
|
console.log(
|
||||||
|
boxen(
|
||||||
|
chalk.white.bold(
|
||||||
|
`✅ Successfully updated ${result.updatedTasks.length} tasks`
|
||||||
|
) +
|
||||||
|
'\n\n' +
|
||||||
|
result.updatedTasks
|
||||||
|
.map(
|
||||||
|
(update) =>
|
||||||
|
`${chalk.cyan(update.taskId)}: ${this.getStatusDisplay(update.oldStatus)} → ${this.getStatusDisplay(update.newStatus)}`
|
||||||
|
)
|
||||||
|
.join('\n'),
|
||||||
|
{
|
||||||
|
padding: 1,
|
||||||
|
borderColor: 'green',
|
||||||
|
borderStyle: 'round',
|
||||||
|
margin: { top: 1 }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show storage info
|
||||||
|
console.log(chalk.gray(`\nUsing ${result.storageType} storage`));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get colored status display
|
||||||
|
*/
|
||||||
|
private getStatusDisplay(status: TaskStatus): string {
|
||||||
|
const statusColors: Record<TaskStatus, (text: string) => string> = {
|
||||||
|
pending: chalk.yellow,
|
||||||
|
'in-progress': chalk.blue,
|
||||||
|
done: chalk.green,
|
||||||
|
deferred: chalk.gray,
|
||||||
|
cancelled: chalk.red,
|
||||||
|
blocked: chalk.red,
|
||||||
|
review: chalk.magenta,
|
||||||
|
completed: chalk.green
|
||||||
|
};
|
||||||
|
|
||||||
|
const colorFn = statusColors[status] || chalk.white;
|
||||||
|
return colorFn(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the last command result (useful for testing or chaining)
|
||||||
|
*/
|
||||||
|
getLastResult(): SetStatusResult | undefined {
|
||||||
|
return this.lastResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static method to register this command on an existing program
|
||||||
|
* This is for gradual migration - allows commands.js to use this
|
||||||
|
*/
|
||||||
|
static registerOn(program: Command): Command {
|
||||||
|
const setStatusCommand = new SetStatusCommand();
|
||||||
|
program.addCommand(setStatusCommand);
|
||||||
|
return setStatusCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alternative registration that returns the command for chaining
|
||||||
|
* Can also configure the command name if needed
|
||||||
|
*/
|
||||||
|
static register(program: Command, name?: string): SetStatusCommand {
|
||||||
|
const setStatusCommand = new SetStatusCommand(name);
|
||||||
|
program.addCommand(setStatusCommand);
|
||||||
|
return setStatusCommand;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory function to create and configure the set-status command
|
||||||
|
*/
|
||||||
|
export function createSetStatusCommand(): SetStatusCommand {
|
||||||
|
return new SetStatusCommand();
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ export { ListTasksCommand } from './commands/list.command.js';
|
|||||||
export { ShowCommand } from './commands/show.command.js';
|
export { ShowCommand } from './commands/show.command.js';
|
||||||
export { AuthCommand } from './commands/auth.command.js';
|
export { AuthCommand } from './commands/auth.command.js';
|
||||||
export { ContextCommand } from './commands/context.command.js';
|
export { ContextCommand } from './commands/context.command.js';
|
||||||
|
export { SetStatusCommand } from './commands/set-status.command.js';
|
||||||
|
|
||||||
// UI utilities (for other commands to use)
|
// UI utilities (for other commands to use)
|
||||||
export * as ui from './utils/ui.js';
|
export * as ui from './utils/ui.js';
|
||||||
|
|||||||
@@ -1,5 +1,16 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## 0.25.0-rc.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- [#1201](https://github.com/eyaltoledano/claude-task-master/pull/1201) [`83af314`](https://github.com/eyaltoledano/claude-task-master/commit/83af314879fc0e563581161c60d2bd089899313e) Thanks [@losolosol](https://github.com/losolosol)! - Added a Start Build button to the VSCODE Task Properties Right Panel
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [[`137ef36`](https://github.com/eyaltoledano/claude-task-master/commit/137ef362789a9cdfdb1925e35e0438c1fa6c69ee)]:
|
||||||
|
- task-master-ai@0.27.0-rc.0
|
||||||
|
|
||||||
## 0.24.2
|
## 0.24.2
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"displayName": "TaskMaster",
|
"displayName": "TaskMaster",
|
||||||
"description": "A visual Kanban board interface for TaskMaster projects in VS Code",
|
"description": "A visual Kanban board interface for TaskMaster projects in VS Code",
|
||||||
"version": "0.24.2",
|
"version": "0.25.0-rc.0",
|
||||||
"publisher": "Hamster",
|
"publisher": "Hamster",
|
||||||
"icon": "assets/icon.png",
|
"icon": "assets/icon.png",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -240,7 +240,7 @@
|
|||||||
"check-types": "tsc --noEmit"
|
"check-types": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"task-master-ai": "0.26.0"
|
"task-master-ai": "*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
|
|||||||
7263
package-lock.json
generated
7263
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "task-master-ai",
|
"name": "task-master-ai",
|
||||||
"version": "0.26.0",
|
"version": "0.27.0-rc.0",
|
||||||
"description": "A task management system for ambitious AI-driven development that doesn't overwhelm and confuse Cursor.",
|
"description": "A task management system for ambitious AI-driven development that doesn't overwhelm and confuse Cursor.",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
"workspaces": ["apps/*", "packages/*", "."],
|
"workspaces": ["apps/*", "packages/*", "."],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npm run build:build-config && cross-env NODE_ENV=production tsdown",
|
"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/**/*'",
|
"dev": "tsdown --watch",
|
||||||
"turbo:dev": "turbo dev",
|
"turbo:dev": "turbo dev",
|
||||||
"turbo:build": "turbo build",
|
"turbo:build": "turbo build",
|
||||||
"turbo:typecheck": "turbo typecheck",
|
"turbo:typecheck": "turbo typecheck",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Shared build configuration for Task Master monorepo",
|
"description": "Shared build configuration for Task Master monorepo",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
"main": "./dist/tsdown.base.js",
|
"main": "./dist/tsdown.base.js",
|
||||||
"types": "./src/tsdown.base.ts",
|
"types": "./src/tsdown.base.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@tm/core",
|
"name": "@tm/core",
|
||||||
"version": "1.0.0",
|
"version": "0.26.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Core library for Task Master - TypeScript task management system",
|
"description": "Core library for Task Master - TypeScript task management system",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -17,6 +17,14 @@ export interface IStorage {
|
|||||||
*/
|
*/
|
||||||
loadTasks(tag?: string): Promise<Task[]>;
|
loadTasks(tag?: string): Promise<Task[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a single task by ID
|
||||||
|
* @param taskId - ID of the task to load
|
||||||
|
* @param tag - Optional tag context for the task
|
||||||
|
* @returns Promise that resolves to the task or null if not found
|
||||||
|
*/
|
||||||
|
loadTask(taskId: string, tag?: string): Promise<Task | null>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save tasks to storage, replacing existing tasks
|
* Save tasks to storage, replacing existing tasks
|
||||||
* @param tasks - Array of tasks to save
|
* @param tasks - Array of tasks to save
|
||||||
@@ -175,6 +183,7 @@ export abstract class BaseStorage implements IStorage {
|
|||||||
|
|
||||||
// Abstract methods that must be implemented by concrete classes
|
// Abstract methods that must be implemented by concrete classes
|
||||||
abstract loadTasks(tag?: string): Promise<Task[]>;
|
abstract loadTasks(tag?: string): Promise<Task[]>;
|
||||||
|
abstract loadTask(taskId: string, tag?: string): Promise<Task | null>;
|
||||||
abstract saveTasks(tasks: Task[], tag?: string): Promise<void>;
|
abstract saveTasks(tasks: Task[], tag?: string): Promise<void>;
|
||||||
abstract appendTasks(tasks: Task[], tag?: string): Promise<void>;
|
abstract appendTasks(tasks: Task[], tag?: string): Promise<void>;
|
||||||
abstract updateTask(
|
abstract updateTask(
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ export class TaskMapper {
|
|||||||
/**
|
/**
|
||||||
* Maps database status to internal status
|
* Maps database status to internal status
|
||||||
*/
|
*/
|
||||||
private static mapStatus(
|
static mapStatus(
|
||||||
status: Database['public']['Enums']['task_status']
|
status: Database['public']['Enums']['task_status']
|
||||||
): Task['status'] {
|
): Task['status'] {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
|
|||||||
@@ -3,6 +3,30 @@ import { Task } from '../types/index.js';
|
|||||||
import { Database } from '../types/database.types.js';
|
import { Database } from '../types/database.types.js';
|
||||||
import { TaskMapper } from '../mappers/TaskMapper.js';
|
import { TaskMapper } from '../mappers/TaskMapper.js';
|
||||||
import { AuthManager } from '../auth/auth-manager.js';
|
import { AuthManager } from '../auth/auth-manager.js';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
// Zod schema for task status validation
|
||||||
|
const TaskStatusSchema = z.enum([
|
||||||
|
'pending',
|
||||||
|
'in-progress',
|
||||||
|
'done',
|
||||||
|
'review',
|
||||||
|
'deferred',
|
||||||
|
'cancelled',
|
||||||
|
'blocked'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Zod schema for task updates
|
||||||
|
const TaskUpdateSchema = z
|
||||||
|
.object({
|
||||||
|
title: z.string().min(1).optional(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
status: TaskStatusSchema.optional(),
|
||||||
|
priority: z.enum(['low', 'medium', 'high', 'critical']).optional(),
|
||||||
|
details: z.string().optional(),
|
||||||
|
testStrategy: z.string().optional()
|
||||||
|
})
|
||||||
|
.partial();
|
||||||
|
|
||||||
export class SupabaseTaskRepository {
|
export class SupabaseTaskRepository {
|
||||||
constructor(private supabase: SupabaseClient<Database>) {}
|
constructor(private supabase: SupabaseClient<Database>) {}
|
||||||
@@ -60,12 +84,22 @@ export class SupabaseTaskRepository {
|
|||||||
return TaskMapper.mapDatabaseTasksToTasks(tasks, depsData || []);
|
return TaskMapper.mapDatabaseTasksToTasks(tasks, depsData || []);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTask(accountId: string, taskId: string): Promise<Task | null> {
|
async getTask(_projectId: string, taskId: string): Promise<Task | null> {
|
||||||
|
// Get the current context to determine briefId (projectId not used in Supabase context)
|
||||||
|
const authManager = AuthManager.getInstance();
|
||||||
|
const context = authManager.getContext();
|
||||||
|
|
||||||
|
if (!context || !context.briefId) {
|
||||||
|
throw new Error(
|
||||||
|
'No brief selected. Please select a brief first using: tm context brief'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const { data, error } = await this.supabase
|
const { data, error } = await this.supabase
|
||||||
.from('tasks')
|
.from('tasks')
|
||||||
.select('*')
|
.select('*')
|
||||||
.eq('account_id', accountId)
|
.eq('brief_id', context.briefId)
|
||||||
.eq('id', taskId)
|
.eq('display_id', taskId.toUpperCase())
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -107,4 +141,84 @@ export class SupabaseTaskRepository {
|
|||||||
dependenciesByTaskId
|
dependenciesByTaskId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateTask(
|
||||||
|
projectId: string,
|
||||||
|
taskId: string,
|
||||||
|
updates: Partial<Task>
|
||||||
|
): Promise<Task> {
|
||||||
|
// Get the current context to determine briefId
|
||||||
|
const authManager = AuthManager.getInstance();
|
||||||
|
const context = authManager.getContext();
|
||||||
|
|
||||||
|
if (!context || !context.briefId) {
|
||||||
|
throw new Error(
|
||||||
|
'No brief selected. Please select a brief first using: tm context brief'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate updates using Zod schema
|
||||||
|
try {
|
||||||
|
TaskUpdateSchema.parse(updates);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof z.ZodError) {
|
||||||
|
const errorMessages = error.errors
|
||||||
|
.map((err) => `${err.path.join('.')}: ${err.message}`)
|
||||||
|
.join(', ');
|
||||||
|
throw new Error(`Invalid task update data: ${errorMessages}`);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert Task fields to database fields - only include fields that actually exist in the database
|
||||||
|
const dbUpdates: any = {};
|
||||||
|
|
||||||
|
if (updates.title !== undefined) dbUpdates.title = updates.title;
|
||||||
|
if (updates.description !== undefined)
|
||||||
|
dbUpdates.description = updates.description;
|
||||||
|
if (updates.status !== undefined)
|
||||||
|
dbUpdates.status = this.mapStatusToDatabase(updates.status);
|
||||||
|
if (updates.priority !== undefined) dbUpdates.priority = updates.priority;
|
||||||
|
// Skip fields that don't exist in database schema: details, testStrategy, etc.
|
||||||
|
|
||||||
|
// Update the task
|
||||||
|
const { error } = await this.supabase
|
||||||
|
.from('tasks')
|
||||||
|
.update(dbUpdates)
|
||||||
|
.eq('brief_id', context.briefId)
|
||||||
|
.eq('display_id', taskId.toUpperCase());
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw new Error(`Failed to update task: ${error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the updated task by fetching it
|
||||||
|
const updatedTask = await this.getTask(projectId, taskId);
|
||||||
|
if (!updatedTask) {
|
||||||
|
throw new Error(`Failed to retrieve updated task ${taskId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps internal status to database status
|
||||||
|
*/
|
||||||
|
private mapStatusToDatabase(
|
||||||
|
status: string
|
||||||
|
): Database['public']['Enums']['task_status'] {
|
||||||
|
switch (status) {
|
||||||
|
case 'pending':
|
||||||
|
return 'todo';
|
||||||
|
case 'in-progress':
|
||||||
|
case 'in_progress': // Accept both formats
|
||||||
|
return 'in_progress';
|
||||||
|
case 'done':
|
||||||
|
return 'done';
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
`Invalid task status: ${status}. Valid statuses are: pending, in-progress, done`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -360,4 +360,73 @@ export class TaskService {
|
|||||||
async setActiveTag(tag: string): Promise<void> {
|
async setActiveTag(tag: string): Promise<void> {
|
||||||
await this.configManager.setActiveTag(tag);
|
await this.configManager.setActiveTag(tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update task status
|
||||||
|
*/
|
||||||
|
async updateTaskStatus(
|
||||||
|
taskId: string | number,
|
||||||
|
newStatus: TaskStatus,
|
||||||
|
tag?: string
|
||||||
|
): Promise<{
|
||||||
|
success: boolean;
|
||||||
|
oldStatus: TaskStatus;
|
||||||
|
newStatus: TaskStatus;
|
||||||
|
taskId: string;
|
||||||
|
}> {
|
||||||
|
// Ensure we have storage
|
||||||
|
if (!this.storage) {
|
||||||
|
throw new TaskMasterError(
|
||||||
|
'Storage not initialized',
|
||||||
|
ERROR_CODES.STORAGE_ERROR
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use provided tag or get active tag
|
||||||
|
const activeTag = tag || this.getActiveTag();
|
||||||
|
|
||||||
|
const taskIdStr = String(taskId);
|
||||||
|
|
||||||
|
// TODO: For now, assume it's a regular task and just try to update directly
|
||||||
|
// In the future, we can add subtask support if needed
|
||||||
|
if (taskIdStr.includes('.')) {
|
||||||
|
throw new TaskMasterError(
|
||||||
|
'Subtask status updates not yet supported in API storage',
|
||||||
|
ERROR_CODES.NOT_IMPLEMENTED
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current task to get old status (simple, direct approach)
|
||||||
|
let currentTask: Task | null;
|
||||||
|
try {
|
||||||
|
// Try to get the task directly
|
||||||
|
currentTask = await this.storage.loadTask(taskIdStr, activeTag);
|
||||||
|
} catch (error) {
|
||||||
|
throw new TaskMasterError(
|
||||||
|
`Failed to load task ${taskIdStr}`,
|
||||||
|
ERROR_CODES.TASK_NOT_FOUND,
|
||||||
|
{ taskId: taskIdStr },
|
||||||
|
error as Error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentTask) {
|
||||||
|
throw new TaskMasterError(
|
||||||
|
`Task ${taskIdStr} not found`,
|
||||||
|
ERROR_CODES.TASK_NOT_FOUND
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldStatus = currentTask.status;
|
||||||
|
|
||||||
|
// Simple, direct update - just change the status
|
||||||
|
await this.storage.updateTask(taskIdStr, { status: newStatus }, activeTag);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
oldStatus,
|
||||||
|
newStatus,
|
||||||
|
taskId: taskIdStr
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -223,14 +223,6 @@ export class ApiStorage implements IStorage {
|
|||||||
await this.ensureInitialized();
|
await this.ensureInitialized();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (tag) {
|
|
||||||
// Check if task is in tag
|
|
||||||
const tagData = this.tagsCache.get(tag);
|
|
||||||
if (!tagData || !tagData.tasks.includes(taskId)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.retryOperation(() =>
|
return await this.retryOperation(() =>
|
||||||
this.repository.getTask(this.projectId, taskId)
|
this.repository.getTask(this.projectId, taskId)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -102,6 +102,14 @@ export class FileStorage implements IStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a single task by ID from the tasks.json file
|
||||||
|
*/
|
||||||
|
async loadTask(taskId: string, tag?: string): Promise<Task | null> {
|
||||||
|
const tasks = await this.loadTasks(tag);
|
||||||
|
return tasks.find((task) => task.id === taskId) || null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save tasks for a specific tag in the single tasks.json file
|
* Save tasks for a specific tag in the single tasks.json file
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -175,6 +175,22 @@ export class TaskMasterCore {
|
|||||||
await this.configManager.setActiveTag(tag);
|
await this.configManager.setActiveTag(tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update task status
|
||||||
|
*/
|
||||||
|
async updateTaskStatus(
|
||||||
|
taskId: string | number,
|
||||||
|
newStatus: TaskStatus,
|
||||||
|
tag?: string
|
||||||
|
): Promise<{
|
||||||
|
success: boolean;
|
||||||
|
oldStatus: TaskStatus;
|
||||||
|
newStatus: TaskStatus;
|
||||||
|
taskId: string;
|
||||||
|
}> {
|
||||||
|
return this.taskService.updateTaskStatus(taskId, newStatus, tag);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close and cleanup resources
|
* Close and cleanup resources
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -20,14 +20,14 @@ import {
|
|||||||
ListTasksCommand,
|
ListTasksCommand,
|
||||||
ShowCommand,
|
ShowCommand,
|
||||||
AuthCommand,
|
AuthCommand,
|
||||||
ContextCommand
|
ContextCommand,
|
||||||
|
SetStatusCommand
|
||||||
} from '@tm/cli';
|
} from '@tm/cli';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
parsePRD,
|
parsePRD,
|
||||||
updateTasks,
|
updateTasks,
|
||||||
generateTaskFiles,
|
generateTaskFiles,
|
||||||
setTaskStatus,
|
|
||||||
listTasks,
|
listTasks,
|
||||||
expandTask,
|
expandTask,
|
||||||
expandAllTasks,
|
expandAllTasks,
|
||||||
@@ -1684,63 +1684,9 @@ function registerCommands(programInstance) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// set-status command
|
// Register the set-status command from @tm/cli
|
||||||
programInstance
|
// Handles task status updates with proper error handling and validation
|
||||||
.command('set-status')
|
SetStatusCommand.registerOn(programInstance);
|
||||||
.alias('mark')
|
|
||||||
.alias('set')
|
|
||||||
.description('Set the status of a task')
|
|
||||||
.option(
|
|
||||||
'-i, --id <id>',
|
|
||||||
'Task ID (can be comma-separated for multiple tasks)'
|
|
||||||
)
|
|
||||||
.option(
|
|
||||||
'-s, --status <status>',
|
|
||||||
`New status (one of: ${TASK_STATUS_OPTIONS.join(', ')})`
|
|
||||||
)
|
|
||||||
.option(
|
|
||||||
'-f, --file <file>',
|
|
||||||
'Path to the tasks file',
|
|
||||||
TASKMASTER_TASKS_FILE
|
|
||||||
)
|
|
||||||
.option('--tag <tag>', 'Specify tag context for task operations')
|
|
||||||
.action(async (options) => {
|
|
||||||
// Initialize TaskMaster
|
|
||||||
const taskMaster = initTaskMaster({
|
|
||||||
tasksPath: options.file || true,
|
|
||||||
tag: options.tag
|
|
||||||
});
|
|
||||||
|
|
||||||
const taskId = options.id;
|
|
||||||
const status = options.status;
|
|
||||||
|
|
||||||
if (!taskId || !status) {
|
|
||||||
console.error(chalk.red('Error: Both --id and --status are required'));
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isValidTaskStatus(status)) {
|
|
||||||
console.error(
|
|
||||||
chalk.red(
|
|
||||||
`Error: Invalid status value: ${status}. Use one of: ${TASK_STATUS_OPTIONS.join(', ')}`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
const tag = taskMaster.getCurrentTag();
|
|
||||||
|
|
||||||
displayCurrentTagIndicator(tag);
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
chalk.blue(`Setting status of task(s) ${taskId} to: ${status}`)
|
|
||||||
);
|
|
||||||
|
|
||||||
await setTaskStatus(taskMaster.getTasksPath(), taskId, status, {
|
|
||||||
projectRoot: taskMaster.getProjectRoot(),
|
|
||||||
tag
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// NEW: Register the new list command from @tm/cli
|
// NEW: Register the new list command from @tm/cli
|
||||||
// This command handles all its own configuration and logic
|
// This command handles all its own configuration and logic
|
||||||
|
|||||||
Reference in New Issue
Block a user