Skip to content

Commit 62dce17

Browse files
committed
feat(deps): improve dependency management
1 parent ae8d835 commit 62dce17

3 files changed

Lines changed: 159 additions & 42 deletions

File tree

src/core/taskmaster/TaskMaster.ts

Lines changed: 136 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,58 @@ export class TaskMaster {
177177
);
178178
}
179179

180+
// TODO: done
181+
/**
182+
* @description Fixes the IDs of all tasks and subtasks in tasks.json to be sequential
183+
* by reorganizing them incrementally starting from 1.
184+
* This method ensures better organization and readability of the task list.
185+
*/
186+
public async fixIdsAsync(): Promise<void> {
187+
const oraOptions = {
188+
text: "Fixing task IDs...",
189+
successText: chalk.bgGreen("Task IDs fixed successfully!"),
190+
failText: chalk.bgRed("Failed to fix task IDs"),
191+
};
192+
193+
await oraPromise(async () => {
194+
// Read the current tasks
195+
const tasks = await this.getTasksContentAsync();
196+
197+
// Sort tasks by their current ID to maintain logical order
198+
tasks.master.tasks.sort((a, b) => a.id - b.id);
199+
200+
// Fix main task IDs
201+
const idMap = new Map<number, number>();
202+
for (const [index, task] of tasks.master.tasks.entries()) {
203+
const oldId = task.id;
204+
const newId = index + 1;
205+
if (oldId !== newId) {
206+
idMap.set(oldId, newId);
207+
task.id = newId;
208+
}
209+
}
210+
211+
// Fix subtask IDs and update dependencies
212+
for (const task of tasks.master.tasks) {
213+
if (task.subtasks && task.subtasks.length > 0) {
214+
for (const [index, subtask] of task.subtasks.entries()) {
215+
subtask.id = index + 1;
216+
}
217+
}
218+
}
219+
220+
// Write the updated tasks back to the file
221+
await writeFile(this._tasksFilePath, JSON.stringify(tasks, null, 2));
222+
223+
// Update the internal tasks file path if needed
224+
this._tasksFilePath = this._tasksFilePath;
225+
226+
// Ensure dependencies are valid after ID reorganization
227+
await this.fixDependenciesAsync();
228+
await this.validateDependenciesAsync();
229+
}, oraOptions);
230+
}
231+
180232
// TODO: done
181233
/**
182234
* @description Fixes the format of the tasks.json file if necessary
@@ -570,21 +622,30 @@ export class TaskMaster {
570622
if (showParent) {
571623
hasTasks = true;
572624
const title = truncate(task.title, MAX_TITLE_TRUNC_LENGTH);
625+
const dependencies =
626+
task.dependencies.length > 0
627+
? `deps: ${chalk.cyan(task.dependencies.join(","))}`
628+
: `deps: ${chalk.gray("none")}`;
573629
output +=
574630
`${chalk.bgGreen.bold(`#${task.id}`)} ${chalk.magenta(title)} ` +
575631
`[status: ${formatStatus(task.status)}] - ` +
576-
`[priority: ${formatPriority(task.priority)}]\n`;
632+
`[priority: ${formatPriority(task.priority)}] - ` +
633+
`[${dependencies}]\n`;
577634

578635
// Only show matching subtasks
579636
if (withSubtasks && matchingSubtasks.length > 0) {
580637
for (const { subtask } of matchingSubtasks) {
581638
const subTitle = truncate(subtask.title, MAX_TITLE_TRUNC_LENGTH);
582639
const hierarchicalId = `${task.id}.${subtask.id}`;
640+
const subDependencies =
641+
subtask.dependencies.length > 0
642+
? `deps: ${chalk.cyan(subtask.dependencies.join(","))}`
643+
: `deps: ${chalk.gray("none")}`;
583644
output +=
584645
` ${chalk.dim("↳")} ${chalk.bold(`#${hierarchicalId}`)} ` +
585646
`${chalk.magenta(subTitle)} [status: ${formatStatus(
586647
subtask.status,
587-
)}]\n`;
648+
)}] - [${subDependencies}]\n`;
588649
}
589650
}
590651
}
@@ -920,7 +981,7 @@ export class TaskMaster {
920981
subtaskId: number,
921982
parentId: number,
922983
): Promise<void> {
923-
await this.clearAllDependenciesAsync(subtaskId.toString());
984+
await this.deleteAllDepsFromTaskAsync(subtaskId.toString());
924985
await this.executeCommandAsync(
925986
`Converting task ${subtaskId} to subtask of ${parentId}...`,
926987
`Task ${subtaskId} converted to subtask successfully!`,
@@ -934,6 +995,7 @@ export class TaskMaster {
934995
// Deleting Methods
935996
// ==============================================
936997

998+
// TODO: done
937999
/**
9381000
* @description Delete a task by ID (including subtasks)
9391001
* @param id The ID of the task to remove
@@ -958,6 +1020,7 @@ export class TaskMaster {
9581020
}
9591021
}
9601022

1023+
// TODO: done
9611024
/**
9621025
* @description Delete a specific subtask
9631026
* @param hierarchicalId The hierarchical ID of the subtask
@@ -987,6 +1050,7 @@ export class TaskMaster {
9871050
}
9881051
}
9891052

1053+
// TODO: done
9901054
/**
9911055
* @description Deletes all subtasks from a specific task
9921056
* @param id The ID of the task to clear subtasks from
@@ -1016,6 +1080,39 @@ export class TaskMaster {
10161080
}
10171081
}
10181082

1083+
// TODO: done
1084+
/**
1085+
* @description Clears all dependencies for the specified task or subtask.
1086+
* @param taskId The task ID or hierarchical ID of the subtask
1087+
*/
1088+
public async deleteAllDepsFromTaskAsync(taskId: string): Promise<void> {
1089+
const tasks = await this.getTasksContentAsync();
1090+
const dependencyIds = await this.getAllDependenciesAsync(tasks, taskId);
1091+
1092+
if (dependencyIds.length === 0) {
1093+
console.log(chalk.yellow(`No dependencies found for task ${taskId}.`));
1094+
return;
1095+
}
1096+
1097+
// For subtasks, dependency IDs are already in the correct format
1098+
// For main tasks, dependency IDs are numbers that need to be converted to string
1099+
const isSubtask = taskId.includes(".");
1100+
1101+
for (const dependencyId of dependencyIds) {
1102+
const dependsOnId = isSubtask
1103+
? dependencyId.toString() // For subtasks, dependencyId is already the full ID
1104+
: dependencyId.toString(); // For main tasks, convert number to string
1105+
1106+
await this.executeCommandAsync(
1107+
`Removing dependency ${dependsOnId} from task ${taskId}...`,
1108+
`Dependency ${dependsOnId} removed successfully from task ${taskId}!`,
1109+
`Failed to remove dependency ${dependsOnId} from task ${taskId}`,
1110+
this._mainCommand,
1111+
["remove-dependency", `--id=${taskId}`, `--depends-on=${dependsOnId}`],
1112+
);
1113+
}
1114+
}
1115+
10191116
// ==============================================
10201117
// Dependencies Methods
10211118
// ==============================================
@@ -1069,34 +1166,6 @@ export class TaskMaster {
10691166
);
10701167
}
10711168

1072-
// TODO: done
1073-
/**
1074-
* @description Clears all dependencies for the specified task or subtask.
1075-
* @param taskId The task ID (either a simple number as string or hierarchical like "1.2")
1076-
*/
1077-
public async clearAllDependenciesAsync(taskId: string): Promise<void> {
1078-
const tasks = await this.getTasksContentAsync();
1079-
const dependencyIds = await this.getAllDependenciesAsync(tasks, taskId);
1080-
1081-
if (dependencyIds.length === 0) {
1082-
console.log(chalk.yellow(`No dependencies found for task ${taskId}.`));
1083-
return;
1084-
}
1085-
1086-
for (const dependencyId of dependencyIds) {
1087-
const dependsOnId = taskId.includes(".")
1088-
? `${taskId.split(".")[0]}.${dependencyId}`
1089-
: dependencyId;
1090-
await this.executeCommandAsync(
1091-
`Removing dependency ${dependsOnId} from task ${taskId}...`,
1092-
`Dependency ${dependsOnId} removed successfully from task ${taskId}!`,
1093-
`Failed to remove dependency ${dependsOnId} from task ${taskId}`,
1094-
this._mainCommand,
1095-
["remove-dependency", `--id=${taskId}`, `--depends-on=${dependsOnId}`],
1096-
);
1097-
}
1098-
}
1099-
11001169
// ==============================================
11011170
// Backup, Restore and Clear Methods
11021171
// ==============================================
@@ -1232,4 +1301,40 @@ export class TaskMaster {
12321301
if (allFilesCleared)
12331302
console.log(chalk.green("All task-related files are cleared!"));
12341303
}
1304+
1305+
// TODO: done
1306+
/**
1307+
* @description Clears all dependencies from all tasks and subtasks
1308+
*/
1309+
public async clearAllDepsAsync(): Promise<void> {
1310+
const { confirm } = await inquirer.prompt({
1311+
type: "confirm",
1312+
name: "confirm",
1313+
message: chalk.red("Clear all dependencies from all tasks and subtasks?"),
1314+
default: false,
1315+
});
1316+
1317+
if (confirm) {
1318+
const oraOptions = {
1319+
text: "Clearing all dependencies...",
1320+
successText: chalk.bgGreen("All dependencies cleared successfully!"),
1321+
failText: chalk.bgRed("Failed to clear all dependencies"),
1322+
};
1323+
1324+
await oraPromise(async () => {
1325+
const tasks = await this.getTasksContentAsync();
1326+
1327+
for (const task of tasks.master.tasks) {
1328+
task.dependencies = [];
1329+
if (task.subtasks && task.subtasks.length > 0) {
1330+
for (const subtask of task.subtasks) {
1331+
subtask.dependencies = [];
1332+
}
1333+
}
1334+
}
1335+
1336+
await writeFile(this._tasksFilePath, JSON.stringify(tasks, null, 2));
1337+
}, oraOptions);
1338+
}
1339+
}
12351340
}

src/core/taskmaster/exec.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,14 @@ export async function tmaiManageAsync() {
312312
await tmai.listAsync(tasks, TASKS_STATUSES.join(","), true, true);
313313
break;
314314
}
315+
case "tmai-deletealldepsfromtask": {
316+
await tmai.listAsync(tasks, TASKS_STATUSES.join(","), true, true);
317+
const taskId = await askHybridTaskIdAsync(mainIDs, subtasksIDs);
318+
await tmai.deleteAllDepsFromTaskAsync(taskId);
319+
tasks = await tmai.getTasksContentAsync();
320+
await tmai.listAsync(tasks, TASKS_STATUSES.join(","), true, true);
321+
break;
322+
}
315323
}
316324
break;
317325
}
@@ -348,12 +356,6 @@ export async function tmaiDependenciesAsync() {
348356
await tmai.fixDependenciesAsync();
349357
break;
350358
}
351-
case "tmai-clearalldeps": {
352-
await tmai.listAsync(tasks, TASKS_STATUSES.join(","), false, true);
353-
const taskId = await askHybridTaskIdAsync(mainIDs, subtasksIDs);
354-
await tmai.clearAllDependenciesAsync(taskId);
355-
break;
356-
}
357359
default:
358360
console.log("Invalid option selected.");
359361
}
@@ -391,6 +393,10 @@ export async function tmaiBackupRestoreClearAsync() {
391393
}
392394
break;
393395
}
396+
case "tmai-clearalldeps": {
397+
await tmai.clearAllDepsAsync();
398+
break;
399+
}
394400
case "tmai-clearallsubtasks": {
395401
await tmai.clearAllSubtasksAsync();
396402
break;

src/prompt.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -287,9 +287,13 @@ export const tmaiDeleteTasksMenu_prompt = [
287287
value: "tmai-deletesubtask",
288288
},
289289
{
290-
name: `${emoji.get("wastebasket")} Clear all subtasks from a task`,
290+
name: `${emoji.get("wastebasket")} Delete all subtasks from a task`,
291291
value: "tmai-deleteallsubtasksfromtask",
292292
},
293+
{
294+
name: `${emoji.get("wastebasket")} Delete all dependencies from a task`,
295+
value: "tmai-deletealldepsfromtask",
296+
},
293297
],
294298
},
295299
];
@@ -320,10 +324,6 @@ export const tmaiDepsMenu_prompt = [
320324
name: `${emoji.get("wrench")} Fix dependencies`,
321325
value: "tmai-fixdeps",
322326
},
323-
{
324-
name: `${emoji.get("broken_heart")} Clear all dependencies from a task`,
325-
value: "tmai-clearalldeps",
326-
},
327327
],
328328
},
329329
];
@@ -382,6 +382,12 @@ export const tmaiBackupRestoreClearClear_prompt = [
382382
),
383383
value: "tmai-restore",
384384
},
385+
{
386+
name: chalk.red(
387+
`${emoji.get("broom")} Clear all dependencies only (for all tasks)`,
388+
),
389+
value: "tmai-clearalldeps",
390+
},
385391
{
386392
name: chalk.red(
387393
`${emoji.get("broom")} Clear all subtasks only (excluding main tasks)`,

0 commit comments

Comments
 (0)