@dalmasonto/taskflow-mcp 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,59 +10,39 @@ export async function getAgentInstructions() {
10
10
  const blockedCount = db.prepare("SELECT COUNT(*) AS c FROM tasks WHERE status = 'blocked'").get().c;
11
11
  const unreadNotifs = db.prepare('SELECT COUNT(*) AS c FROM notifications WHERE read = 0').get().c;
12
12
  const instructions = {
13
- role: 'You are connected to TaskFlow, a local-first task and time tracking system. You have access to tools for managing projects, tasks, timers, analytics, activity logs, notifications, and settings.',
14
- startup_checklist: [
15
- 'Call list_projects to see all projects and their task counts.',
16
- 'Identify which project is relevant to the current work. Use search_projects by name if unsure. If multiple projects match, ask the user to confirm which one before proceeding.',
17
- 'Call list_tasks with status "in_progress" to see what is actively being worked on.',
18
- 'Call list_tasks with status "blocked" to see what is stuck and might need your help.',
19
- 'Call list_notifications with unread_only=true to check for pending notifications.',
13
+ role: 'TaskFlow local-first task & time tracker with MCP integration.',
14
+ startup: [
15
+ 'search_projects to find the current project (try name variants; confirm with user if ambiguous)',
16
+ 'list_tasks status="in_progress" and status="blocked"',
17
+ 'list_notifications unread_only=true',
20
18
  ],
21
- current_state: {
19
+ state: {
22
20
  projects: projectCount,
23
- total_tasks: taskCount,
21
+ tasks: taskCount,
24
22
  in_progress: inProgressCount,
25
23
  blocked: blockedCount,
26
- unread_notifications: unreadNotifs,
24
+ unread: unreadNotifs,
27
25
  },
28
- behavioral_rules: [
29
- 'At the start of a conversation, search for the current project by name using search_projects. Try multiple name variants if needed. If multiple projects match or you are unsure, ask the user to confirm which project to work on before creating or updating tasks. Always link new tasks to the confirmed project.',
30
- 'Before starting any coding work, check if a matching task exists in TaskFlow. If one exists, call start_timer on it to track time.',
31
- 'When you finish a piece of work, call stop_timer with the appropriate final_status: "done" if complete, "partial_done" if more work remains, "blocked" if you hit a blocker.',
32
- 'If you pause to wait for user input or switch context, call pause_timer on the active task.',
33
- 'If you encounter a blocker (missing dependency, unclear requirement, failing test you cannot fix), call stop_timer with final_status "blocked". This closes the active session and marks the task as blocked in one step. Update the task description to explain what is blocking it.',
34
- 'Before starting work on a task, check its dependencies array. If any dependency task is still "not_started" or "in_progress", set the task to "blocked" — do not start working on it until its dependencies are complete.',
35
- 'When the user asks "what should I work on?" or seems unsure what to do next, call list_tasks filtered by priority "critical" or "high" and status "not_started" to surface the most important unblocked work.',
36
- 'After completing a task, check if any blocked tasks had a dependency on it and might now be unblocked.',
37
- 'Periodically check list_notifications with unread_only=true and surface important ones to the user.',
38
- 'Before creating a new task, call list_tasks (or search_tasks) to check if a similar task already exists. Avoid creating duplicates — if a matching task exists, update it or start working on it instead of creating a new one.',
39
- 'When you finish work on a task, you MUST mark it as done. Call stop_timer with final_status "done" this closes the session and sets the task to done in one step. Never leave a completed task in "in_progress" or "paused" status.',
40
- 'When you create new work items (files, features, fixes), create corresponding tasks in TaskFlow to keep the tracker in sync.',
41
- 'Proactively create tasks on the fly for any work you are doing bug fixes, improvements, feature implementations, refactors. Create the task, start a timer, do the work, then stop the timer with the final status. Every meaningful unit of work should be tracked, even if the user did not explicitly ask you to create a task for it.',
42
- 'Use Markdown in description fields — headings, bullet lists, code blocks, bold/italic. Task and project descriptions render Markdown in the UI, so well-formatted descriptions are more readable for the user.',
43
- 'When debugging, use the log_debug tool to record your process — what you are investigating, what you tried, error messages, hypotheses, and findings. Link it to the relevant task with task_id. This creates a visible breadcrumb trail in the Activity Pulse that helps the user understand your reasoning.',
26
+ rules: [
27
+ // Task tracking
28
+ 'Proactively create tasks for ALL substantial work (features, bugs, refactors). search_tasks first to avoid duplicates. Link tasks to the confirmed project.',
29
+ 'Timer lifecycle: start_timer work stop_timer(final_status). Use "done"/"partial_done"/"blocked". pause_timer when waiting for input.',
30
+ 'MUST stop_timer with "done" when work is complete. Never leave finished tasks in "in_progress" or "paused".',
31
+ // Dependencies
32
+ 'Check task dependencies before starting. If any dep is incomplete, set task to "blocked".',
33
+ 'After completing a task, check if blocked tasks depending on it can be unblocked.',
34
+ // Prioritization
35
+ 'When user is unsure what to work on: list_tasks priority="critical"/"high" status="not_started".',
36
+ // Logging
37
+ 'Use log_debug with task_id to record debugging steps, hypotheses, findings, decisions. Use Markdown formatting.',
38
+ // Formatting
39
+ 'Use Markdown in descriptionsheadings, bullets, code blocks, bold. The UI renders it.',
44
40
  ],
45
- task_workflow: {
46
- description: 'The standard lifecycle of a task through the system',
47
- flow: 'not_started → in_progress (start_timer) → paused (pause_timer) → in_progress (start_timer) → done/partial_done/blocked (stop_timer)',
48
- valid_transitions: {
49
- not_started: ['in_progress', 'blocked'],
50
- in_progress: ['paused', 'blocked', 'partial_done', 'done'],
51
- paused: ['in_progress', 'blocked', 'partial_done', 'done'],
52
- blocked: ['not_started', 'in_progress'],
53
- partial_done: ['in_progress', 'done'],
54
- done: ['in_progress'],
55
- },
56
- },
41
+ workflow: 'not_started → in_progress (start_timer) → paused (pause_timer) → done/partial_done/blocked (stop_timer)',
57
42
  tips: [
58
- 'Tasks have tags use them to find related work (e.g. list_tasks with tag "bug" or "frontend").',
59
- 'Tasks can have dependencies check the dependencies array to understand task ordering.',
60
- 'Use get_analytics for a high-level overview of time spent and task completion rates.',
61
- 'Use search_tasks to find tasks by keyword when you are not sure of the exact task ID.',
62
- 'Use search_projects to find a project by name. Try the repo name, directory name, or common abbreviations.',
63
- 'Read task descriptions carefully — they often contain implementation details, acceptance criteria, or context that will help you do better work.',
64
- 'Write task descriptions in Markdown: use ## headings for sections, - bullet lists for steps, ```code blocks``` for snippets, and **bold** for emphasis. The UI renders Markdown natively.',
65
- 'When creating tasks that depend on others, set dependencies and use the "blocked" status to indicate the blocking relationship. This shows up in the dependency graph.',
43
+ 'Filter by tags (list_tasks tag="bug"), search by keyword (search_tasks), get full detail (get_task id).',
44
+ 'get_analytics for time spent & completion rates. Dependencies show in the dependency graph.',
45
+ 'list_tasks/search_tasks return compact summaries. Use get_task(id) to read full descriptions.',
66
46
  ],
67
47
  };
68
48
  return successResponse(instructions);
@@ -10,6 +10,28 @@ function parseTask(row) {
10
10
  tags: JSON.parse(row.tags),
11
11
  };
12
12
  }
13
+ /** Compact task for list/search responses — omits description, null fields, and empty arrays */
14
+ function parseTaskCompact(row) {
15
+ const tags = JSON.parse(row.tags);
16
+ const deps = JSON.parse(row.dependencies);
17
+ const result = {
18
+ id: row.id,
19
+ title: row.title,
20
+ status: row.status,
21
+ priority: row.priority,
22
+ };
23
+ if (row.project_id != null)
24
+ result.project_id = row.project_id;
25
+ if (tags.length > 0)
26
+ result.tags = tags;
27
+ if (deps.length > 0)
28
+ result.dependencies = deps;
29
+ if (row.due_date != null)
30
+ result.due_date = row.due_date;
31
+ if (row.estimated_time != null)
32
+ result.estimated_time = row.estimated_time;
33
+ return result;
34
+ }
13
35
  function detectCycle(taskId, proposedDeps) {
14
36
  const db = getDb();
15
37
  const allTasks = db.prepare('SELECT id, dependencies FROM tasks').all();
@@ -116,7 +138,7 @@ export async function listTasks(params) {
116
138
  return tags.includes(params.tag);
117
139
  });
118
140
  }
119
- return successResponse(rows.map(parseTask));
141
+ return successResponse(rows.map(parseTaskCompact));
120
142
  }
121
143
  export async function getTask(params) {
122
144
  const db = getDb();
@@ -236,6 +258,21 @@ export async function updateTaskStatus(params) {
236
258
  return errorResponse(`Cannot transition from "${currentStatus}" to "${params.status}"`, 'INVALID_TRANSITION');
237
259
  }
238
260
  const ts = now();
261
+ // Auto-close any active timer session when leaving an active state
262
+ const terminalStatuses = ['done', 'partial_done', 'blocked', 'paused'];
263
+ if (terminalStatuses.includes(params.status)) {
264
+ const openSession = db.prepare('SELECT * FROM sessions WHERE task_id = ? AND end IS NULL').get(params.id);
265
+ if (openSession) {
266
+ db.prepare('UPDATE sessions SET end = ? WHERE id = ?').run(ts, openSession.id);
267
+ const duration = new Date(ts).getTime() - new Date(openSession.start).getTime();
268
+ logActivity('timer_stopped', row.title, {
269
+ detail: `Auto-stopped: duration ${duration}ms, status → ${params.status}`,
270
+ entityType: 'task',
271
+ entityId: params.id,
272
+ });
273
+ broadcastChange('timer', 'timer_stopped', { task_id: params.id, session: { ...openSession, end: ts, duration }, task_status: params.status });
274
+ }
275
+ }
239
276
  db.prepare('UPDATE tasks SET status = ?, updated_at = ? WHERE id = ?').run(params.status, ts, params.id);
240
277
  // Log appropriate action
241
278
  let statusAction;
@@ -295,7 +332,7 @@ export async function searchTasks(params) {
295
332
  const db = getDb();
296
333
  const like = `%${params.query}%`;
297
334
  const rows = db.prepare(`SELECT * FROM tasks WHERE title LIKE ? COLLATE NOCASE OR description LIKE ? COLLATE NOCASE`).all(like, like);
298
- return successResponse(rows.map(parseTask));
335
+ return successResponse(rows.map(parseTaskCompact));
299
336
  }
300
337
  // ─── MCP registration ─────────────────────────────────────────────────
301
338
  export function registerTaskTools(server) {
@@ -59,13 +59,25 @@ export async function stopTimer(params) {
59
59
  return errorResponse('Task not found', 'NOT_FOUND');
60
60
  // Find open session
61
61
  const session = db.prepare('SELECT * FROM sessions WHERE task_id = ? AND end IS NULL').get(params.task_id);
62
- if (!session)
63
- return errorResponse('No active timer session for this task', 'NO_ACTIVE_SESSION');
64
62
  const finalStatus = params.final_status ?? 'done';
65
63
  const endTime = now();
66
- const duration = new Date(endTime).getTime() - new Date(session.start).getTime();
67
- // Close session
68
- db.prepare('UPDATE sessions SET end = ? WHERE id = ?').run(endTime, session.id);
64
+ let duration;
65
+ let closedSession;
66
+ if (session) {
67
+ // Normal path — close the active session
68
+ duration = new Date(endTime).getTime() - new Date(session.start).getTime();
69
+ db.prepare('UPDATE sessions SET end = ? WHERE id = ?').run(endTime, session.id);
70
+ closedSession = { ...session, end: endTime };
71
+ }
72
+ else {
73
+ // Fallback — session was already closed (e.g. by orphan cleanup or update_task_status)
74
+ // Still update the task status so the caller's intent is honored
75
+ const lastSession = db.prepare('SELECT * FROM sessions WHERE task_id = ? ORDER BY start DESC LIMIT 1').get(params.task_id);
76
+ if (!lastSession)
77
+ return errorResponse('No timer sessions found for this task', 'NO_ACTIVE_SESSION');
78
+ duration = new Date(lastSession.end).getTime() - new Date(lastSession.start).getTime();
79
+ closedSession = lastSession;
80
+ }
69
81
  // Update task to final_status
70
82
  db.prepare('UPDATE tasks SET status = ?, updated_at = ? WHERE id = ?').run(finalStatus, endTime, params.task_id);
71
83
  logActivity('timer_stopped', task.title, {
@@ -80,7 +92,7 @@ export async function stopTimer(params) {
80
92
  else if (finalStatus === 'partial_done') {
81
93
  logActivity('task_partial_done', task.title, { entityType: 'task', entityId: params.task_id });
82
94
  }
83
- const stoppedSession = { ...session, end: endTime, duration };
95
+ const stoppedSession = { ...closedSession, duration };
84
96
  broadcastChange('timer', 'timer_stopped', { task_id: params.task_id, session: stoppedSession, task_status: finalStatus });
85
97
  return successResponse(stoppedSession);
86
98
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dalmasonto/taskflow-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "MCP server for TaskFlow — manage projects, tasks, timers, analytics via AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",