@hasna/todos 0.10.9 → 0.10.11

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,7 @@ __export(exports_task_files, {
9812
9812
  listActiveFiles: () => listActiveFiles,
9813
9813
  getTaskFile: () => getTaskFile,
9814
9814
  findTasksByFile: () => findTasksByFile,
9815
+ detectFileConflicts: () => detectFileConflicts,
9815
9816
  bulkFindTasksByFiles: () => bulkFindTasksByFiles,
9816
9817
  bulkAddTaskFiles: () => bulkAddTaskFiles,
9817
9818
  addTaskFile: () => addTaskFile
@@ -9853,6 +9854,24 @@ function removeTaskFile(taskId, path, db) {
9853
9854
  const result = d.run("DELETE FROM task_files WHERE task_id = ? AND path = ?", [taskId, path]);
9854
9855
  return result.changes > 0;
9855
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
+ }
9856
9875
  function bulkFindTasksByFiles(paths, db) {
9857
9876
  const d = db || getDatabase();
9858
9877
  if (paths.length === 0)
@@ -10817,8 +10836,6 @@ var init_mcp = __esm(() => {
10817
10836
  "heartbeat"
10818
10837
  ]);
10819
10838
  STANDARD_EXCLUDED = new Set([
10820
- "get_org_chart",
10821
- "set_reports_to",
10822
10839
  "rename_agent",
10823
10840
  "delete_agent",
10824
10841
  "unarchive_agent",
@@ -12263,14 +12280,32 @@ In Progress:`);
12263
12280
  });
12264
12281
  }
12265
12282
  if (shouldRegisterTool("get_org_chart")) {
12266
- server.tool("get_org_chart", "Get agent org chart showing reporting hierarchy.", {}, async () => {
12283
+ server.tool("get_org_chart", "Get agent org chart showing reporting hierarchy with roles, titles, capabilities, and activity status.", {
12284
+ format: exports_external.enum(["text", "json"]).optional().describe("Output format (default: text)"),
12285
+ role: exports_external.string().optional().describe("Filter by agent role (e.g. 'lead', 'developer')"),
12286
+ active_only: exports_external.coerce.boolean().optional().describe("Only show agents active in last 30 min")
12287
+ }, async ({ format, role, active_only }) => {
12267
12288
  try {
12268
- let render = function(nodes, indent = 0) {
12289
+ let filterTree = function(nodes) {
12290
+ return nodes.map((n) => ({ ...n, reports: filterTree(n.reports) })).filter((n) => {
12291
+ if (role && n.agent.role !== role)
12292
+ return false;
12293
+ if (active_only) {
12294
+ const lastSeen = new Date(n.agent.last_seen_at).getTime();
12295
+ if (now2 - lastSeen > ACTIVE_MS)
12296
+ return false;
12297
+ }
12298
+ return true;
12299
+ });
12300
+ }, render = function(nodes, indent = 0) {
12269
12301
  return nodes.map((n) => {
12270
12302
  const prefix = " ".repeat(indent);
12271
12303
  const title = n.agent.title ? ` \u2014 ${n.agent.title}` : "";
12272
- const level = n.agent.level ? ` (${n.agent.level})` : "";
12273
- const line = `${prefix}${n.agent.name}${title}${level}`;
12304
+ const level = n.agent.level ? ` [${n.agent.level}]` : "";
12305
+ const caps = n.agent.capabilities?.length > 0 ? ` {${n.agent.capabilities.join(", ")}}` : "";
12306
+ const lastSeen = new Date(n.agent.last_seen_at).getTime();
12307
+ const active = now2 - lastSeen < ACTIVE_MS ? " \u25CF" : " \u25CB";
12308
+ const line = `${prefix}${active} ${n.agent.name}${title}${level}${caps}`;
12274
12309
  const children = n.reports.length > 0 ? `
12275
12310
  ` + render(n.reports, indent + 1) : "";
12276
12311
  return line + children;
@@ -12278,7 +12313,14 @@ In Progress:`);
12278
12313
  `);
12279
12314
  };
12280
12315
  const { getOrgChart: getOrgChart2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
12281
- const tree = getOrgChart2();
12316
+ let tree = getOrgChart2();
12317
+ const now2 = Date.now();
12318
+ const ACTIVE_MS = 1800000;
12319
+ if (role || active_only)
12320
+ tree = filterTree(tree);
12321
+ if (format === "json") {
12322
+ return { content: [{ type: "text", text: JSON.stringify(tree, null, 2) }] };
12323
+ }
12282
12324
  const text = tree.length > 0 ? render(tree) : "No agents registered.";
12283
12325
  return { content: [{ type: "text", text }] };
12284
12326
  } catch (e) {
@@ -13289,7 +13331,7 @@ Claimed: ${formatTask(result.claimed)}`);
13289
13331
  return { contents: [{ uri: "todos://agents", text: JSON.stringify(agents, null, 2), mimeType: "application/json" }] };
13290
13332
  });
13291
13333
  if (shouldRegisterTool("add_task_file")) {
13292
- 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.", {
13334
+ 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.", {
13293
13335
  task_id: exports_external.string().describe("Task ID"),
13294
13336
  path: exports_external.string().describe("File path (relative or absolute)"),
13295
13337
  paths: exports_external.array(exports_external.string()).optional().describe("Multiple file paths to add at once"),
@@ -13298,14 +13340,41 @@ Claimed: ${formatTask(result.claimed)}`);
13298
13340
  note: exports_external.string().optional().describe("Note about why this file is linked")
13299
13341
  }, async ({ task_id, path, paths: multiplePaths, status, agent_id, note }) => {
13300
13342
  try {
13301
- const { addTaskFile: addTaskFile2, bulkAddTaskFiles: bulkAddTaskFiles2 } = (init_task_files(), __toCommonJS(exports_task_files));
13343
+ const { addTaskFile: addTaskFile2, bulkAddTaskFiles: bulkAddTaskFiles2, detectFileConflicts: detectFileConflicts2 } = (init_task_files(), __toCommonJS(exports_task_files));
13302
13344
  const resolvedId = resolveId(task_id);
13345
+ let addedFiles;
13303
13346
  if (multiplePaths && multiplePaths.length > 0) {
13304
13347
  const allPaths = path ? [path, ...multiplePaths] : multiplePaths;
13305
- const files = bulkAddTaskFiles2(resolvedId, allPaths, agent_id);
13306
- return { content: [{ type: "text", text: `${files.length} file(s) linked to task ${resolvedId.slice(0, 8)}` }] };
13348
+ addedFiles = bulkAddTaskFiles2(resolvedId, allPaths, agent_id);
13349
+ const conflicts2 = detectFileConflicts2(resolvedId, allPaths);
13350
+ if (conflicts2.length > 0) {
13351
+ return {
13352
+ content: [{
13353
+ type: "text",
13354
+ text: JSON.stringify({
13355
+ added: addedFiles.length,
13356
+ conflicts: conflicts2,
13357
+ warning: `${conflicts2.length} file(s) already claimed by other in-progress tasks`
13358
+ }, null, 2)
13359
+ }]
13360
+ };
13361
+ }
13362
+ return { content: [{ type: "text", text: `${addedFiles.length} file(s) linked to task ${resolvedId.slice(0, 8)}` }] };
13307
13363
  }
13308
13364
  const file = addTaskFile2({ task_id: resolvedId, path, status, agent_id, note });
13365
+ const conflicts = detectFileConflicts2(resolvedId, [path]);
13366
+ if (conflicts.length > 0) {
13367
+ return {
13368
+ content: [{
13369
+ type: "text",
13370
+ text: JSON.stringify({
13371
+ file,
13372
+ conflicts,
13373
+ warning: `${path} is already claimed by another in-progress task`
13374
+ }, null, 2)
13375
+ }]
13376
+ };
13377
+ }
13309
13378
  return { content: [{ type: "text", text: `${file.status} ${file.path} \u2192 task ${resolvedId.slice(0, 8)}` }] };
13310
13379
  } catch (e) {
13311
13380
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -22,6 +22,18 @@ 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[];
25
37
  export interface BulkFileResult {
26
38
  path: string;
27
39
  tasks: TaskFile[];
@@ -1 +1 @@
1
- {"version":3,"file":"task-files.d.ts","sourceRoot":"","sources":["../../src/db/task-files.ts"],"names":[],"mappings":"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"}
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,7 @@ __export(exports_task_files, {
1357
1357
  listActiveFiles: () => listActiveFiles,
1358
1358
  getTaskFile: () => getTaskFile,
1359
1359
  findTasksByFile: () => findTasksByFile,
1360
+ detectFileConflicts: () => detectFileConflicts,
1360
1361
  bulkFindTasksByFiles: () => bulkFindTasksByFiles,
1361
1362
  bulkAddTaskFiles: () => bulkAddTaskFiles,
1362
1363
  addTaskFile: () => addTaskFile
@@ -1398,6 +1399,24 @@ function removeTaskFile(taskId, path, db) {
1398
1399
  const result = d.run("DELETE FROM task_files WHERE task_id = ? AND path = ?", [taskId, path]);
1399
1400
  return result.changes > 0;
1400
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
+ }
1401
1420
  function bulkFindTasksByFiles(paths, db) {
1402
1421
  const d = db || getDatabase();
1403
1422
  if (paths.length === 0)
@@ -8422,8 +8441,6 @@ var MINIMAL_TOOLS = new Set([
8422
8441
  "heartbeat"
8423
8442
  ]);
8424
8443
  var STANDARD_EXCLUDED = new Set([
8425
- "get_org_chart",
8426
- "set_reports_to",
8427
8444
  "rename_agent",
8428
8445
  "delete_agent",
8429
8446
  "unarchive_agent",
@@ -9991,14 +10008,32 @@ In Progress:`);
9991
10008
  });
9992
10009
  }
9993
10010
  if (shouldRegisterTool("get_org_chart")) {
9994
- server.tool("get_org_chart", "Get agent org chart showing reporting hierarchy.", {}, async () => {
10011
+ server.tool("get_org_chart", "Get agent org chart showing reporting hierarchy with roles, titles, capabilities, and activity status.", {
10012
+ format: exports_external.enum(["text", "json"]).optional().describe("Output format (default: text)"),
10013
+ role: exports_external.string().optional().describe("Filter by agent role (e.g. 'lead', 'developer')"),
10014
+ active_only: exports_external.coerce.boolean().optional().describe("Only show agents active in last 30 min")
10015
+ }, async ({ format, role, active_only }) => {
9995
10016
  try {
9996
- let render = function(nodes, indent = 0) {
10017
+ let filterTree = function(nodes) {
10018
+ return nodes.map((n) => ({ ...n, reports: filterTree(n.reports) })).filter((n) => {
10019
+ if (role && n.agent.role !== role)
10020
+ return false;
10021
+ if (active_only) {
10022
+ const lastSeen = new Date(n.agent.last_seen_at).getTime();
10023
+ if (now2 - lastSeen > ACTIVE_MS)
10024
+ return false;
10025
+ }
10026
+ return true;
10027
+ });
10028
+ }, render = function(nodes, indent = 0) {
9997
10029
  return nodes.map((n) => {
9998
10030
  const prefix = " ".repeat(indent);
9999
10031
  const title = n.agent.title ? ` \u2014 ${n.agent.title}` : "";
10000
- const level = n.agent.level ? ` (${n.agent.level})` : "";
10001
- const line = `${prefix}${n.agent.name}${title}${level}`;
10032
+ const level = n.agent.level ? ` [${n.agent.level}]` : "";
10033
+ const caps = n.agent.capabilities?.length > 0 ? ` {${n.agent.capabilities.join(", ")}}` : "";
10034
+ const lastSeen = new Date(n.agent.last_seen_at).getTime();
10035
+ const active = now2 - lastSeen < ACTIVE_MS ? " \u25CF" : " \u25CB";
10036
+ const line = `${prefix}${active} ${n.agent.name}${title}${level}${caps}`;
10002
10037
  const children = n.reports.length > 0 ? `
10003
10038
  ` + render(n.reports, indent + 1) : "";
10004
10039
  return line + children;
@@ -10006,7 +10041,14 @@ if (shouldRegisterTool("get_org_chart")) {
10006
10041
  `);
10007
10042
  };
10008
10043
  const { getOrgChart: getOrgChart2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
10009
- const tree = getOrgChart2();
10044
+ let tree = getOrgChart2();
10045
+ const now2 = Date.now();
10046
+ const ACTIVE_MS = 1800000;
10047
+ if (role || active_only)
10048
+ tree = filterTree(tree);
10049
+ if (format === "json") {
10050
+ return { content: [{ type: "text", text: JSON.stringify(tree, null, 2) }] };
10051
+ }
10010
10052
  const text = tree.length > 0 ? render(tree) : "No agents registered.";
10011
10053
  return { content: [{ type: "text", text }] };
10012
10054
  } catch (e) {
@@ -11017,7 +11059,7 @@ server.resource("agents", "todos://agents", { description: "All registered agent
11017
11059
  return { contents: [{ uri: "todos://agents", text: JSON.stringify(agents, null, 2), mimeType: "application/json" }] };
11018
11060
  });
11019
11061
  if (shouldRegisterTool("add_task_file")) {
11020
- 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.", {
11062
+ 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.", {
11021
11063
  task_id: exports_external.string().describe("Task ID"),
11022
11064
  path: exports_external.string().describe("File path (relative or absolute)"),
11023
11065
  paths: exports_external.array(exports_external.string()).optional().describe("Multiple file paths to add at once"),
@@ -11026,14 +11068,41 @@ if (shouldRegisterTool("add_task_file")) {
11026
11068
  note: exports_external.string().optional().describe("Note about why this file is linked")
11027
11069
  }, async ({ task_id, path, paths: multiplePaths, status, agent_id, note }) => {
11028
11070
  try {
11029
- const { addTaskFile: addTaskFile2, bulkAddTaskFiles: bulkAddTaskFiles2 } = (init_task_files(), __toCommonJS(exports_task_files));
11071
+ const { addTaskFile: addTaskFile2, bulkAddTaskFiles: bulkAddTaskFiles2, detectFileConflicts: detectFileConflicts2 } = (init_task_files(), __toCommonJS(exports_task_files));
11030
11072
  const resolvedId = resolveId(task_id);
11073
+ let addedFiles;
11031
11074
  if (multiplePaths && multiplePaths.length > 0) {
11032
11075
  const allPaths = path ? [path, ...multiplePaths] : multiplePaths;
11033
- const files = bulkAddTaskFiles2(resolvedId, allPaths, agent_id);
11034
- return { content: [{ type: "text", text: `${files.length} file(s) linked to task ${resolvedId.slice(0, 8)}` }] };
11076
+ addedFiles = bulkAddTaskFiles2(resolvedId, allPaths, agent_id);
11077
+ const conflicts2 = detectFileConflicts2(resolvedId, allPaths);
11078
+ if (conflicts2.length > 0) {
11079
+ return {
11080
+ content: [{
11081
+ type: "text",
11082
+ text: JSON.stringify({
11083
+ added: addedFiles.length,
11084
+ conflicts: conflicts2,
11085
+ warning: `${conflicts2.length} file(s) already claimed by other in-progress tasks`
11086
+ }, null, 2)
11087
+ }]
11088
+ };
11089
+ }
11090
+ return { content: [{ type: "text", text: `${addedFiles.length} file(s) linked to task ${resolvedId.slice(0, 8)}` }] };
11035
11091
  }
11036
11092
  const file = addTaskFile2({ task_id: resolvedId, path, status, agent_id, note });
11093
+ const conflicts = detectFileConflicts2(resolvedId, [path]);
11094
+ if (conflicts.length > 0) {
11095
+ return {
11096
+ content: [{
11097
+ type: "text",
11098
+ text: JSON.stringify({
11099
+ file,
11100
+ conflicts,
11101
+ warning: `${path} is already claimed by another in-progress task`
11102
+ }, null, 2)
11103
+ }]
11104
+ };
11105
+ }
11037
11106
  return { content: [{ type: "text", text: `${file.status} ${file.path} \u2192 task ${resolvedId.slice(0, 8)}` }] };
11038
11107
  } catch (e) {
11039
11108
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/todos",
3
- "version": "0.10.9",
3
+ "version": "0.10.11",
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",