@hasna/todos 0.10.8 → 0.10.10

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.
package/dist/cli/index.js CHANGED
@@ -9812,6 +9812,8 @@ __export(exports_task_files, {
9812
9812
  listActiveFiles: () => listActiveFiles,
9813
9813
  getTaskFile: () => getTaskFile,
9814
9814
  findTasksByFile: () => findTasksByFile,
9815
+ detectFileConflicts: () => detectFileConflicts,
9816
+ bulkFindTasksByFiles: () => bulkFindTasksByFiles,
9815
9817
  bulkAddTaskFiles: () => bulkAddTaskFiles,
9816
9818
  addTaskFile: () => addTaskFile
9817
9819
  });
@@ -9852,6 +9854,50 @@ function removeTaskFile(taskId, path, db) {
9852
9854
  const result = d.run("DELETE FROM task_files WHERE task_id = ? AND path = ?", [taskId, path]);
9853
9855
  return result.changes > 0;
9854
9856
  }
9857
+ function detectFileConflicts(taskId, paths, db) {
9858
+ const d = db || getDatabase();
9859
+ if (paths.length === 0)
9860
+ return [];
9861
+ const placeholders = paths.map(() => "?").join(", ");
9862
+ const rows = d.query(`
9863
+ SELECT tf.path, tf.agent_id AS conflicting_agent_id, t.id AS conflicting_task_id,
9864
+ t.title AS conflicting_task_title, t.status AS conflicting_task_status
9865
+ FROM task_files tf
9866
+ JOIN tasks t ON tf.task_id = t.id
9867
+ WHERE tf.path IN (${placeholders})
9868
+ AND tf.task_id != ?
9869
+ AND tf.status != 'removed'
9870
+ AND t.status = 'in_progress'
9871
+ ORDER BY tf.updated_at DESC
9872
+ `).all(...paths, taskId);
9873
+ return rows;
9874
+ }
9875
+ function bulkFindTasksByFiles(paths, db) {
9876
+ const d = db || getDatabase();
9877
+ if (paths.length === 0)
9878
+ return [];
9879
+ const placeholders = paths.map(() => "?").join(", ");
9880
+ const rows = d.query(`SELECT tf.*, t.status AS task_status FROM task_files tf
9881
+ JOIN tasks t ON tf.task_id = t.id
9882
+ WHERE tf.path IN (${placeholders}) AND tf.status != 'removed'
9883
+ ORDER BY tf.updated_at DESC`).all(...paths);
9884
+ const byPath = new Map;
9885
+ for (const path of paths)
9886
+ byPath.set(path, []);
9887
+ for (const row of rows) {
9888
+ byPath.get(row.path)?.push(row);
9889
+ }
9890
+ return paths.map((path) => {
9891
+ const tasks = byPath.get(path) ?? [];
9892
+ const inProgressCount = tasks.filter((t) => t.task_status === "in_progress").length;
9893
+ return {
9894
+ path,
9895
+ tasks,
9896
+ has_conflict: inProgressCount > 1,
9897
+ in_progress_count: inProgressCount
9898
+ };
9899
+ });
9900
+ }
9855
9901
  function listActiveFiles(db) {
9856
9902
  const d = db || getDatabase();
9857
9903
  return d.query(`
@@ -13262,7 +13308,7 @@ Claimed: ${formatTask(result.claimed)}`);
13262
13308
  return { contents: [{ uri: "todos://agents", text: JSON.stringify(agents, null, 2), mimeType: "application/json" }] };
13263
13309
  });
13264
13310
  if (shouldRegisterTool("add_task_file")) {
13265
- server.tool("add_task_file", "Link a file path to a task. Tracks which files an agent is working on. Upserts if same task+path exists.", {
13311
+ server.tool("add_task_file", "Link a file path to a task. Tracks which files an agent is working on. Upserts if same task+path exists. Auto-detects conflicts with other in-progress tasks.", {
13266
13312
  task_id: exports_external.string().describe("Task ID"),
13267
13313
  path: exports_external.string().describe("File path (relative or absolute)"),
13268
13314
  paths: exports_external.array(exports_external.string()).optional().describe("Multiple file paths to add at once"),
@@ -13271,14 +13317,41 @@ Claimed: ${formatTask(result.claimed)}`);
13271
13317
  note: exports_external.string().optional().describe("Note about why this file is linked")
13272
13318
  }, async ({ task_id, path, paths: multiplePaths, status, agent_id, note }) => {
13273
13319
  try {
13274
- const { addTaskFile: addTaskFile2, bulkAddTaskFiles: bulkAddTaskFiles2 } = (init_task_files(), __toCommonJS(exports_task_files));
13320
+ const { addTaskFile: addTaskFile2, bulkAddTaskFiles: bulkAddTaskFiles2, detectFileConflicts: detectFileConflicts2 } = (init_task_files(), __toCommonJS(exports_task_files));
13275
13321
  const resolvedId = resolveId(task_id);
13322
+ let addedFiles;
13276
13323
  if (multiplePaths && multiplePaths.length > 0) {
13277
13324
  const allPaths = path ? [path, ...multiplePaths] : multiplePaths;
13278
- const files = bulkAddTaskFiles2(resolvedId, allPaths, agent_id);
13279
- return { content: [{ type: "text", text: `${files.length} file(s) linked to task ${resolvedId.slice(0, 8)}` }] };
13325
+ addedFiles = bulkAddTaskFiles2(resolvedId, allPaths, agent_id);
13326
+ const conflicts2 = detectFileConflicts2(resolvedId, allPaths);
13327
+ if (conflicts2.length > 0) {
13328
+ return {
13329
+ content: [{
13330
+ type: "text",
13331
+ text: JSON.stringify({
13332
+ added: addedFiles.length,
13333
+ conflicts: conflicts2,
13334
+ warning: `${conflicts2.length} file(s) already claimed by other in-progress tasks`
13335
+ }, null, 2)
13336
+ }]
13337
+ };
13338
+ }
13339
+ return { content: [{ type: "text", text: `${addedFiles.length} file(s) linked to task ${resolvedId.slice(0, 8)}` }] };
13280
13340
  }
13281
13341
  const file = addTaskFile2({ task_id: resolvedId, path, status, agent_id, note });
13342
+ const conflicts = detectFileConflicts2(resolvedId, [path]);
13343
+ if (conflicts.length > 0) {
13344
+ return {
13345
+ content: [{
13346
+ type: "text",
13347
+ text: JSON.stringify({
13348
+ file,
13349
+ conflicts,
13350
+ warning: `${path} is already claimed by another in-progress task`
13351
+ }, null, 2)
13352
+ }]
13353
+ };
13354
+ }
13282
13355
  return { content: [{ type: "text", text: `${file.status} ${file.path} \u2192 task ${resolvedId.slice(0, 8)}` }] };
13283
13356
  } catch (e) {
13284
13357
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -13318,6 +13391,19 @@ ${lines.join(`
13318
13391
  }
13319
13392
  });
13320
13393
  }
13394
+ if (shouldRegisterTool("bulk_find_tasks_by_files")) {
13395
+ server.tool("bulk_find_tasks_by_files", "Check multiple file paths at once for task/agent collisions. Returns per-path task list, in-progress count, and conflict flag.", {
13396
+ paths: exports_external.array(exports_external.string()).describe("Array of file paths to check")
13397
+ }, async ({ paths }) => {
13398
+ try {
13399
+ const { bulkFindTasksByFiles: bulkFindTasksByFiles2 } = (init_task_files(), __toCommonJS(exports_task_files));
13400
+ const results = bulkFindTasksByFiles2(paths);
13401
+ return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
13402
+ } catch (e) {
13403
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
13404
+ }
13405
+ });
13406
+ }
13321
13407
  if (shouldRegisterTool("list_active_files")) {
13322
13408
  server.tool("list_active_files", "Return all files linked to in-progress tasks across all agents \u2014 the bird's-eye view of what's being worked on right now.", {
13323
13409
  project_id: exports_external.string().optional().describe("Filter by project")
@@ -22,6 +22,25 @@ export declare function listTaskFiles(taskId: string, db?: Database): TaskFile[]
22
22
  export declare function findTasksByFile(path: string, db?: Database): TaskFile[];
23
23
  export declare function updateTaskFileStatus(taskId: string, path: string, status: TaskFile["status"], agentId?: string, db?: Database): TaskFile | null;
24
24
  export declare function removeTaskFile(taskId: string, path: string, db?: Database): boolean;
25
+ export interface FileConflict {
26
+ path: string;
27
+ conflicting_task_id: string;
28
+ conflicting_agent_id: string | null;
29
+ conflicting_task_title: string;
30
+ conflicting_task_status: string;
31
+ }
32
+ /**
33
+ * Check if adding a file to a task would conflict with another in-progress task.
34
+ * Returns conflicts (not a hard block — caller decides what to do).
35
+ */
36
+ export declare function detectFileConflicts(taskId: string, paths: string[], db?: Database): FileConflict[];
37
+ export interface BulkFileResult {
38
+ path: string;
39
+ tasks: TaskFile[];
40
+ has_conflict: boolean;
41
+ in_progress_count: number;
42
+ }
43
+ export declare function bulkFindTasksByFiles(paths: string[], db?: Database): BulkFileResult[];
25
44
  export interface ActiveFileInfo {
26
45
  path: string;
27
46
  file_status: TaskFile["status"];
@@ -1 +1 @@
1
- {"version":3,"file":"task-files.d.ts","sourceRoot":"","sources":["../../src/db/task-files.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAG3C,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,CAAC;IACnE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAwB5E;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,IAAI,CAGtE;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,QAAQ,EAAE,CAKvE;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,QAAQ,EAAE,CAKvE;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,EAC1B,OAAO,CAAC,EAAE,MAAM,EAChB,EAAE,CAAC,EAAE,QAAQ,GACZ,QAAQ,GAAG,IAAI,CAWjB;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,CAOnF;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAChC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,wBAAgB,eAAe,CAAC,EAAE,CAAC,EAAE,QAAQ,GAAG,cAAc,EAAE,CAwB/D;AAED,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EAAE,EACf,OAAO,CAAC,EAAE,MAAM,EAChB,EAAE,CAAC,EAAE,QAAQ,GACZ,QAAQ,EAAE,CAUZ"}
1
+ {"version":3,"file":"task-files.d.ts","sourceRoot":"","sources":["../../src/db/task-files.ts"],"names":[],"mappings":"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"}
package/dist/mcp/index.js CHANGED
@@ -1357,6 +1357,8 @@ __export(exports_task_files, {
1357
1357
  listActiveFiles: () => listActiveFiles,
1358
1358
  getTaskFile: () => getTaskFile,
1359
1359
  findTasksByFile: () => findTasksByFile,
1360
+ detectFileConflicts: () => detectFileConflicts,
1361
+ bulkFindTasksByFiles: () => bulkFindTasksByFiles,
1360
1362
  bulkAddTaskFiles: () => bulkAddTaskFiles,
1361
1363
  addTaskFile: () => addTaskFile
1362
1364
  });
@@ -1397,6 +1399,50 @@ function removeTaskFile(taskId, path, db) {
1397
1399
  const result = d.run("DELETE FROM task_files WHERE task_id = ? AND path = ?", [taskId, path]);
1398
1400
  return result.changes > 0;
1399
1401
  }
1402
+ function detectFileConflicts(taskId, paths, db) {
1403
+ const d = db || getDatabase();
1404
+ if (paths.length === 0)
1405
+ return [];
1406
+ const placeholders = paths.map(() => "?").join(", ");
1407
+ const rows = d.query(`
1408
+ SELECT tf.path, tf.agent_id AS conflicting_agent_id, t.id AS conflicting_task_id,
1409
+ t.title AS conflicting_task_title, t.status AS conflicting_task_status
1410
+ FROM task_files tf
1411
+ JOIN tasks t ON tf.task_id = t.id
1412
+ WHERE tf.path IN (${placeholders})
1413
+ AND tf.task_id != ?
1414
+ AND tf.status != 'removed'
1415
+ AND t.status = 'in_progress'
1416
+ ORDER BY tf.updated_at DESC
1417
+ `).all(...paths, taskId);
1418
+ return rows;
1419
+ }
1420
+ function bulkFindTasksByFiles(paths, db) {
1421
+ const d = db || getDatabase();
1422
+ if (paths.length === 0)
1423
+ return [];
1424
+ const placeholders = paths.map(() => "?").join(", ");
1425
+ const rows = d.query(`SELECT tf.*, t.status AS task_status FROM task_files tf
1426
+ JOIN tasks t ON tf.task_id = t.id
1427
+ WHERE tf.path IN (${placeholders}) AND tf.status != 'removed'
1428
+ ORDER BY tf.updated_at DESC`).all(...paths);
1429
+ const byPath = new Map;
1430
+ for (const path of paths)
1431
+ byPath.set(path, []);
1432
+ for (const row of rows) {
1433
+ byPath.get(row.path)?.push(row);
1434
+ }
1435
+ return paths.map((path) => {
1436
+ const tasks = byPath.get(path) ?? [];
1437
+ const inProgressCount = tasks.filter((t) => t.task_status === "in_progress").length;
1438
+ return {
1439
+ path,
1440
+ tasks,
1441
+ has_conflict: inProgressCount > 1,
1442
+ in_progress_count: inProgressCount
1443
+ };
1444
+ });
1445
+ }
1400
1446
  function listActiveFiles(db) {
1401
1447
  const d = db || getDatabase();
1402
1448
  return d.query(`
@@ -10990,7 +11036,7 @@ server.resource("agents", "todos://agents", { description: "All registered agent
10990
11036
  return { contents: [{ uri: "todos://agents", text: JSON.stringify(agents, null, 2), mimeType: "application/json" }] };
10991
11037
  });
10992
11038
  if (shouldRegisterTool("add_task_file")) {
10993
- server.tool("add_task_file", "Link a file path to a task. Tracks which files an agent is working on. Upserts if same task+path exists.", {
11039
+ server.tool("add_task_file", "Link a file path to a task. Tracks which files an agent is working on. Upserts if same task+path exists. Auto-detects conflicts with other in-progress tasks.", {
10994
11040
  task_id: exports_external.string().describe("Task ID"),
10995
11041
  path: exports_external.string().describe("File path (relative or absolute)"),
10996
11042
  paths: exports_external.array(exports_external.string()).optional().describe("Multiple file paths to add at once"),
@@ -10999,14 +11045,41 @@ if (shouldRegisterTool("add_task_file")) {
10999
11045
  note: exports_external.string().optional().describe("Note about why this file is linked")
11000
11046
  }, async ({ task_id, path, paths: multiplePaths, status, agent_id, note }) => {
11001
11047
  try {
11002
- const { addTaskFile: addTaskFile2, bulkAddTaskFiles: bulkAddTaskFiles2 } = (init_task_files(), __toCommonJS(exports_task_files));
11048
+ const { addTaskFile: addTaskFile2, bulkAddTaskFiles: bulkAddTaskFiles2, detectFileConflicts: detectFileConflicts2 } = (init_task_files(), __toCommonJS(exports_task_files));
11003
11049
  const resolvedId = resolveId(task_id);
11050
+ let addedFiles;
11004
11051
  if (multiplePaths && multiplePaths.length > 0) {
11005
11052
  const allPaths = path ? [path, ...multiplePaths] : multiplePaths;
11006
- const files = bulkAddTaskFiles2(resolvedId, allPaths, agent_id);
11007
- return { content: [{ type: "text", text: `${files.length} file(s) linked to task ${resolvedId.slice(0, 8)}` }] };
11053
+ addedFiles = bulkAddTaskFiles2(resolvedId, allPaths, agent_id);
11054
+ const conflicts2 = detectFileConflicts2(resolvedId, allPaths);
11055
+ if (conflicts2.length > 0) {
11056
+ return {
11057
+ content: [{
11058
+ type: "text",
11059
+ text: JSON.stringify({
11060
+ added: addedFiles.length,
11061
+ conflicts: conflicts2,
11062
+ warning: `${conflicts2.length} file(s) already claimed by other in-progress tasks`
11063
+ }, null, 2)
11064
+ }]
11065
+ };
11066
+ }
11067
+ return { content: [{ type: "text", text: `${addedFiles.length} file(s) linked to task ${resolvedId.slice(0, 8)}` }] };
11008
11068
  }
11009
11069
  const file = addTaskFile2({ task_id: resolvedId, path, status, agent_id, note });
11070
+ const conflicts = detectFileConflicts2(resolvedId, [path]);
11071
+ if (conflicts.length > 0) {
11072
+ return {
11073
+ content: [{
11074
+ type: "text",
11075
+ text: JSON.stringify({
11076
+ file,
11077
+ conflicts,
11078
+ warning: `${path} is already claimed by another in-progress task`
11079
+ }, null, 2)
11080
+ }]
11081
+ };
11082
+ }
11010
11083
  return { content: [{ type: "text", text: `${file.status} ${file.path} \u2192 task ${resolvedId.slice(0, 8)}` }] };
11011
11084
  } catch (e) {
11012
11085
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -11046,6 +11119,19 @@ ${lines.join(`
11046
11119
  }
11047
11120
  });
11048
11121
  }
11122
+ if (shouldRegisterTool("bulk_find_tasks_by_files")) {
11123
+ server.tool("bulk_find_tasks_by_files", "Check multiple file paths at once for task/agent collisions. Returns per-path task list, in-progress count, and conflict flag.", {
11124
+ paths: exports_external.array(exports_external.string()).describe("Array of file paths to check")
11125
+ }, async ({ paths }) => {
11126
+ try {
11127
+ const { bulkFindTasksByFiles: bulkFindTasksByFiles2 } = (init_task_files(), __toCommonJS(exports_task_files));
11128
+ const results = bulkFindTasksByFiles2(paths);
11129
+ return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
11130
+ } catch (e) {
11131
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
11132
+ }
11133
+ });
11134
+ }
11049
11135
  if (shouldRegisterTool("list_active_files")) {
11050
11136
  server.tool("list_active_files", "Return all files linked to in-progress tasks across all agents \u2014 the bird's-eye view of what's being worked on right now.", {
11051
11137
  project_id: exports_external.string().optional().describe("Filter by project")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/todos",
3
- "version": "0.10.8",
3
+ "version": "0.10.10",
4
4
  "description": "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",