mirror of
https://github.com/eyaltoledano/claude-task-master.git
synced 2025-07-03 23:19:44 +00:00

* feat: added complexity score handling to list tasks * feat: added handling for complexity score in find task by id * test: remove console dir * chore: add changeset * format: fixed formatting issues * ref: reorder imports * feat: updated handling for findTaskById to take complexityReport as input * test: fix findTaskById complexity report testcases * fix: added handling for complexity report path * chore: add changeset * fix: moved complexity report handling to list tasks rather than list tasks direct * fix: add complexity handling to next task in list command * fix: added handling for show cli * fix: fixed next cli command handling * fix: fixed handling for complexity report path in mcp * feat: added handling to get-task * feat: added handling for next-task in mcp * feat: add handling for report path override * chore: remove unecessary changeset * ref: remove unecessary comments * feat: update list and find next task * fix: fixed running tests * fix: fixed findTaskById * fix: fixed findTaskById and tests * fix: fixed addComplexityToTask util * fix: fixed mcp server project root input * chore: cleanup --------- Co-authored-by: Shrey Paharia <shreypaharia@gmail.com>
139 lines
4.5 KiB
JavaScript
139 lines
4.5 KiB
JavaScript
import { log } from '../utils.js';
|
||
import { addComplexityToTask } from '../utils.js';
|
||
|
||
/**
|
||
* Return the next work item:
|
||
* • Prefer an eligible SUBTASK that belongs to any parent task
|
||
* whose own status is `in-progress`.
|
||
* • If no such subtask exists, fall back to the best top-level task
|
||
* (previous behaviour).
|
||
*
|
||
* The function still exports the same name (`findNextTask`) so callers
|
||
* don't need to change. It now always returns an object with
|
||
* ─ id → number (task) or "parentId.subId" (subtask)
|
||
* ─ title → string
|
||
* ─ status → string
|
||
* ─ priority → string ("high" | "medium" | "low")
|
||
* ─ dependencies → array (all IDs expressed in the same dotted form)
|
||
* ─ parentId → number (present only when it's a subtask)
|
||
*
|
||
* @param {Object[]} tasks – full array of top-level tasks, each may contain .subtasks[]
|
||
* @param {Object} [complexityReport=null] - Optional complexity report object
|
||
* @returns {Object|null} – next work item or null if nothing is eligible
|
||
*/
|
||
function findNextTask(tasks, complexityReport = null) {
|
||
// ---------- helpers ----------------------------------------------------
|
||
const priorityValues = { high: 3, medium: 2, low: 1 };
|
||
|
||
const toFullSubId = (parentId, maybeDotId) => {
|
||
// "12.3" -> "12.3"
|
||
// 4 -> "12.4" (numeric / short form)
|
||
if (typeof maybeDotId === 'string' && maybeDotId.includes('.')) {
|
||
return maybeDotId;
|
||
}
|
||
return `${parentId}.${maybeDotId}`;
|
||
};
|
||
|
||
// ---------- build completed-ID set (tasks *and* subtasks) --------------
|
||
const completedIds = new Set();
|
||
tasks.forEach((t) => {
|
||
if (t.status === 'done' || t.status === 'completed') {
|
||
completedIds.add(String(t.id));
|
||
}
|
||
if (Array.isArray(t.subtasks)) {
|
||
t.subtasks.forEach((st) => {
|
||
if (st.status === 'done' || st.status === 'completed') {
|
||
completedIds.add(`${t.id}.${st.id}`);
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
// ---------- 1) look for eligible subtasks ------------------------------
|
||
const candidateSubtasks = [];
|
||
|
||
tasks
|
||
.filter((t) => t.status === 'in-progress' && Array.isArray(t.subtasks))
|
||
.forEach((parent) => {
|
||
parent.subtasks.forEach((st) => {
|
||
const stStatus = (st.status || 'pending').toLowerCase();
|
||
if (stStatus !== 'pending' && stStatus !== 'in-progress') return;
|
||
|
||
const fullDeps =
|
||
st.dependencies?.map((d) => toFullSubId(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}`,
|
||
status: st.status || 'pending',
|
||
priority: st.priority || parent.priority || 'medium',
|
||
dependencies: fullDeps,
|
||
parentId: parent.id
|
||
});
|
||
}
|
||
});
|
||
});
|
||
|
||
if (candidateSubtasks.length > 0) {
|
||
// sort by priority → dep-count → parent-id → sub-id
|
||
candidateSubtasks.sort((a, b) => {
|
||
const pa = priorityValues[a.priority] ?? 2;
|
||
const pb = priorityValues[b.priority] ?? 2;
|
||
if (pb !== pa) return pb - pa;
|
||
|
||
if (a.dependencies.length !== b.dependencies.length)
|
||
return a.dependencies.length - b.dependencies.length;
|
||
|
||
// compare parent then sub-id numerically
|
||
const [aPar, aSub] = a.id.split('.').map(Number);
|
||
const [bPar, bSub] = b.id.split('.').map(Number);
|
||
if (aPar !== bPar) return aPar - bPar;
|
||
return aSub - bSub;
|
||
});
|
||
const nextTask = candidateSubtasks[0];
|
||
|
||
// Add complexity to the task before returning
|
||
if (nextTask && complexityReport) {
|
||
addComplexityToTask(nextTask, complexityReport);
|
||
}
|
||
|
||
return nextTask;
|
||
}
|
||
|
||
// ---------- 2) fall back to top-level tasks (original logic) ------------
|
||
const eligibleTasks = tasks.filter((task) => {
|
||
const status = (task.status || 'pending').toLowerCase();
|
||
if (status !== 'pending' && status !== 'in-progress') return false;
|
||
const deps = task.dependencies ?? [];
|
||
return deps.every((depId) => completedIds.has(String(depId)));
|
||
});
|
||
|
||
if (eligibleTasks.length === 0) return null;
|
||
|
||
const nextTask = eligibleTasks.sort((a, b) => {
|
||
const pa = priorityValues[a.priority || 'medium'] ?? 2;
|
||
const pb = priorityValues[b.priority || 'medium'] ?? 2;
|
||
if (pb !== pa) return pb - pa;
|
||
|
||
const da = (a.dependencies ?? []).length;
|
||
const db = (b.dependencies ?? []).length;
|
||
if (da !== db) return da - db;
|
||
|
||
return a.id - b.id;
|
||
})[0];
|
||
|
||
// Add complexity to the task before returning
|
||
if (nextTask && complexityReport) {
|
||
addComplexityToTask(nextTask, complexityReport);
|
||
}
|
||
|
||
return nextTask;
|
||
}
|
||
|
||
export default findNextTask;
|