@hasna/todos 0.10.11 → 0.10.13

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
@@ -2897,6 +2897,20 @@ var init_database = __esm(() => {
2897
2897
  ALTER TABLE agents ADD COLUMN status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'archived'));
2898
2898
  CREATE INDEX IF NOT EXISTS idx_agents_status ON agents(status);
2899
2899
  INSERT OR IGNORE INTO _migrations (id) VALUES (30);
2900
+ `,
2901
+ `
2902
+ CREATE TABLE IF NOT EXISTS project_agent_roles (
2903
+ id TEXT PRIMARY KEY,
2904
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
2905
+ agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
2906
+ role TEXT NOT NULL,
2907
+ is_lead INTEGER NOT NULL DEFAULT 0,
2908
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
2909
+ UNIQUE(project_id, agent_id, role)
2910
+ );
2911
+ CREATE INDEX IF NOT EXISTS idx_project_agent_roles_project ON project_agent_roles(project_id);
2912
+ CREATE INDEX IF NOT EXISTS idx_project_agent_roles_agent ON project_agent_roles(agent_id);
2913
+ INSERT OR IGNORE INTO _migrations (id) VALUES (31);
2900
2914
  `,
2901
2915
  `
2902
2916
  CREATE TABLE IF NOT EXISTS file_locks (
@@ -2910,7 +2924,7 @@ var init_database = __esm(() => {
2910
2924
  CREATE INDEX IF NOT EXISTS idx_file_locks_path ON file_locks(path);
2911
2925
  CREATE INDEX IF NOT EXISTS idx_file_locks_agent ON file_locks(agent_id);
2912
2926
  CREATE INDEX IF NOT EXISTS idx_file_locks_expires ON file_locks(expires_at);
2913
- INSERT OR IGNORE INTO _migrations (id) VALUES (31);
2927
+ INSERT OR IGNORE INTO _migrations (id) VALUES (32);
2914
2928
  `
2915
2929
  ];
2916
2930
  });
@@ -10432,6 +10446,81 @@ var init_kg = __esm(() => {
10432
10446
  init_database();
10433
10447
  });
10434
10448
 
10449
+ // src/db/project-agent-roles.ts
10450
+ var exports_project_agent_roles = {};
10451
+ __export(exports_project_agent_roles, {
10452
+ setProjectAgentRole: () => setProjectAgentRole,
10453
+ removeProjectAgentRole: () => removeProjectAgentRole,
10454
+ listProjectAgentRoles: () => listProjectAgentRoles,
10455
+ getProjectOrgChart: () => getProjectOrgChart,
10456
+ getAgentProjectRoles: () => getAgentProjectRoles
10457
+ });
10458
+ function rowToRole(row) {
10459
+ return { ...row, is_lead: row.is_lead === 1 };
10460
+ }
10461
+ function setProjectAgentRole(projectId, agentId, role, isLead = false, db) {
10462
+ const d = db || getDatabase();
10463
+ const existing = d.query("SELECT * FROM project_agent_roles WHERE project_id = ? AND agent_id = ? AND role = ?").get(projectId, agentId, role);
10464
+ if (existing) {
10465
+ d.run("UPDATE project_agent_roles SET is_lead = ? WHERE id = ?", [isLead ? 1 : 0, existing.id]);
10466
+ return rowToRole(d.query("SELECT * FROM project_agent_roles WHERE id = ?").get(existing.id));
10467
+ }
10468
+ const id = uuid();
10469
+ d.run("INSERT INTO project_agent_roles (id, project_id, agent_id, role, is_lead, created_at) VALUES (?, ?, ?, ?, ?, ?)", [id, projectId, agentId, role, isLead ? 1 : 0, now()]);
10470
+ return rowToRole(d.query("SELECT * FROM project_agent_roles WHERE id = ?").get(id));
10471
+ }
10472
+ function removeProjectAgentRole(projectId, agentId, role, db) {
10473
+ const d = db || getDatabase();
10474
+ if (role) {
10475
+ return d.run("DELETE FROM project_agent_roles WHERE project_id = ? AND agent_id = ? AND role = ?", [projectId, agentId, role]).changes;
10476
+ }
10477
+ return d.run("DELETE FROM project_agent_roles WHERE project_id = ? AND agent_id = ?", [projectId, agentId]).changes;
10478
+ }
10479
+ function listProjectAgentRoles(projectId, db) {
10480
+ const d = db || getDatabase();
10481
+ return d.query("SELECT * FROM project_agent_roles WHERE project_id = ? ORDER BY role, created_at").all(projectId).map(rowToRole);
10482
+ }
10483
+ function getAgentProjectRoles(agentId, db) {
10484
+ const d = db || getDatabase();
10485
+ return d.query("SELECT * FROM project_agent_roles WHERE agent_id = ? ORDER BY project_id, role").all(agentId).map(rowToRole);
10486
+ }
10487
+ function getProjectOrgChart(projectId, opts, db) {
10488
+ const d = db || getDatabase();
10489
+ const globalTree = getOrgChart(d);
10490
+ const projectRoles = listProjectAgentRoles(projectId, d);
10491
+ const rolesByAgent = new Map;
10492
+ for (const pr of projectRoles) {
10493
+ if (!rolesByAgent.has(pr.agent_id))
10494
+ rolesByAgent.set(pr.agent_id, { roles: [], isLead: false });
10495
+ const entry = rolesByAgent.get(pr.agent_id);
10496
+ entry.roles.push(pr.role);
10497
+ if (pr.is_lead)
10498
+ entry.isLead = true;
10499
+ }
10500
+ function augmentTree(nodes) {
10501
+ return nodes.map((n) => {
10502
+ const override = rolesByAgent.get(n.agent.id);
10503
+ return {
10504
+ ...n,
10505
+ reports: augmentTree(n.reports),
10506
+ project_roles: override?.roles ?? [],
10507
+ is_project_lead: override?.isLead ?? false
10508
+ };
10509
+ }).filter((n) => {
10510
+ if (!opts?.filter_to_project)
10511
+ return true;
10512
+ const hasRole = n.project_roles.length > 0;
10513
+ const hasDescendant = n.reports.length > 0;
10514
+ return hasRole || hasDescendant;
10515
+ });
10516
+ }
10517
+ return augmentTree(globalTree);
10518
+ }
10519
+ var init_project_agent_roles = __esm(() => {
10520
+ init_database();
10521
+ init_agents();
10522
+ });
10523
+
10435
10524
  // src/db/patrol.ts
10436
10525
  var exports_patrol = {};
10437
10526
  __export(exports_patrol, {
@@ -13768,6 +13857,78 @@ ${lines.join(`
13768
13857
  }
13769
13858
  });
13770
13859
  }
13860
+ if (shouldRegisterTool("set_project_agent_role")) {
13861
+ server.tool("set_project_agent_role", "Assign an agent a role on a specific project (client, lead, developer, qa, reviewer, etc.). Per-project roles extend the global org chart.", {
13862
+ project_id: exports_external.string().describe("Project ID"),
13863
+ agent_name: exports_external.string().describe("Agent name"),
13864
+ role: exports_external.string().describe("Role on this project (e.g. 'lead', 'developer', 'qa')"),
13865
+ is_lead: exports_external.coerce.boolean().optional().describe("Whether this agent is the project lead for this role")
13866
+ }, async ({ project_id, agent_name, role, is_lead }) => {
13867
+ try {
13868
+ const { setProjectAgentRole: setProjectAgentRole2 } = (init_project_agent_roles(), __toCommonJS(exports_project_agent_roles));
13869
+ const agent = getAgentByName(agent_name);
13870
+ if (!agent)
13871
+ return { content: [{ type: "text", text: `Agent not found: ${agent_name}` }], isError: true };
13872
+ const pid = resolveId(project_id, "projects");
13873
+ const result = setProjectAgentRole2(pid, agent.id, role, is_lead ?? false);
13874
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
13875
+ } catch (e) {
13876
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
13877
+ }
13878
+ });
13879
+ }
13880
+ if (shouldRegisterTool("get_project_org_chart")) {
13881
+ server.tool("get_project_org_chart", "Get org chart scoped to a project \u2014 global hierarchy with per-project role overrides merged in.", {
13882
+ project_id: exports_external.string().describe("Project ID"),
13883
+ format: exports_external.enum(["text", "json"]).optional().describe("Output format (default: text)"),
13884
+ filter_to_project: exports_external.coerce.boolean().optional().describe("Only show agents with a role on this project")
13885
+ }, async ({ project_id, format, filter_to_project }) => {
13886
+ try {
13887
+ let render = function(nodes, indent = 0) {
13888
+ return nodes.map((n) => {
13889
+ const prefix = " ".repeat(indent);
13890
+ const title = n.agent.title ? ` \u2014 ${n.agent.title}` : "";
13891
+ const globalRole = n.agent.role ? ` [${n.agent.role}]` : "";
13892
+ const projectRoles = n.project_roles.length > 0 ? ` <${n.project_roles.join(", ")}>` : "";
13893
+ const lead = n.is_project_lead ? " \u2605" : "";
13894
+ const lastSeen = new Date(n.agent.last_seen_at).getTime();
13895
+ const active = now2 - lastSeen < ACTIVE_MS ? " \u25CF" : " \u25CB";
13896
+ const line = `${prefix}${active} ${n.agent.name}${title}${globalRole}${projectRoles}${lead}`;
13897
+ const children = n.reports.length > 0 ? `
13898
+ ` + render(n.reports, indent + 1) : "";
13899
+ return line + children;
13900
+ }).join(`
13901
+ `);
13902
+ };
13903
+ const { getProjectOrgChart: getProjectOrgChart2 } = (init_project_agent_roles(), __toCommonJS(exports_project_agent_roles));
13904
+ const pid = resolveId(project_id, "projects");
13905
+ const tree = getProjectOrgChart2(pid, { filter_to_project });
13906
+ if (format === "json") {
13907
+ return { content: [{ type: "text", text: JSON.stringify(tree, null, 2) }] };
13908
+ }
13909
+ const now2 = Date.now();
13910
+ const ACTIVE_MS = 30 * 60 * 1000;
13911
+ const text = tree.length > 0 ? render(tree) : "No agents in this project's org chart.";
13912
+ return { content: [{ type: "text", text }] };
13913
+ } catch (e) {
13914
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
13915
+ }
13916
+ });
13917
+ }
13918
+ if (shouldRegisterTool("list_project_agent_roles")) {
13919
+ server.tool("list_project_agent_roles", "List all agent role assignments for a project.", {
13920
+ project_id: exports_external.string().describe("Project ID")
13921
+ }, async ({ project_id }) => {
13922
+ try {
13923
+ const { listProjectAgentRoles: listProjectAgentRoles2 } = (init_project_agent_roles(), __toCommonJS(exports_project_agent_roles));
13924
+ const pid = resolveId(project_id, "projects");
13925
+ const roles = listProjectAgentRoles2(pid);
13926
+ return { content: [{ type: "text", text: JSON.stringify(roles, null, 2) }] };
13927
+ } catch (e) {
13928
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
13929
+ }
13930
+ });
13931
+ }
13771
13932
  if (shouldRegisterTool("get_capable_agents")) {
13772
13933
  server.tool("get_capable_agents", "Find agents that match given capabilities, sorted by match score.", {
13773
13934
  capabilities: exports_external.array(exports_external.string()).describe("Required capabilities to match against"),
@@ -1 +1 @@
1
- {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAItC,eAAO,MAAM,mBAAmB,KAAK,CAAC;AA6hBtC,wBAAgB,WAAW,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,CAkBrD;AAyRD,wBAAgB,aAAa,IAAI,IAAI,CAKpC;AAED,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AAED,wBAAgB,GAAG,IAAI,MAAM,CAE5B;AAED,wBAAgB,IAAI,IAAI,MAAM,CAE7B;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAK9D;AAED,wBAAgB,gBAAgB,CAAC,KAAK,SAAa,GAAG,MAAM,CAG3D;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAGpD;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA0B9F"}
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAItC,eAAO,MAAM,mBAAmB,KAAK,CAAC;AA4iBtC,wBAAgB,WAAW,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,CAkBrD;AAyRD,wBAAgB,aAAa,IAAI,IAAI,CAKpC;AAED,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AAED,wBAAgB,GAAG,IAAI,MAAM,CAE5B;AAED,wBAAgB,IAAI,IAAI,MAAM,CAE7B;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAK9D;AAED,wBAAgB,gBAAgB,CAAC,KAAK,SAAa,GAAG,MAAM,CAG3D;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAGpD;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA0B9F"}
@@ -0,0 +1,34 @@
1
+ import type { Database } from "bun:sqlite";
2
+ import type { OrgNode } from "./agents.js";
3
+ export interface ProjectAgentRole {
4
+ id: string;
5
+ project_id: string;
6
+ agent_id: string;
7
+ role: string;
8
+ is_lead: boolean;
9
+ created_at: string;
10
+ }
11
+ export interface ProjectAgentRoleRow {
12
+ id: string;
13
+ project_id: string;
14
+ agent_id: string;
15
+ role: string;
16
+ is_lead: number;
17
+ created_at: string;
18
+ }
19
+ export declare function setProjectAgentRole(projectId: string, agentId: string, role: string, isLead?: boolean, db?: Database): ProjectAgentRole;
20
+ export declare function removeProjectAgentRole(projectId: string, agentId: string, role?: string, db?: Database): number;
21
+ export declare function listProjectAgentRoles(projectId: string, db?: Database): ProjectAgentRole[];
22
+ export declare function getAgentProjectRoles(agentId: string, db?: Database): ProjectAgentRole[];
23
+ export interface ProjectOrgNode extends OrgNode {
24
+ project_roles: string[];
25
+ is_project_lead: boolean;
26
+ }
27
+ /**
28
+ * Get org chart scoped to a project. Returns global org chart with per-project
29
+ * role overrides merged in. Agents not in the project are excluded when filter=true.
30
+ */
31
+ export declare function getProjectOrgChart(projectId: string, opts?: {
32
+ filter_to_project?: boolean;
33
+ }, db?: Database): ProjectOrgNode[];
34
+ //# sourceMappingURL=project-agent-roles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-agent-roles.d.ts","sourceRoot":"","sources":["../../src/db/project-agent-roles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAG3C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAMD,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,MAAM,UAAQ,EACd,EAAE,CAAC,EAAE,QAAQ,GACZ,gBAAgB,CAoBlB;AAED,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,MAAM,EACb,EAAE,CAAC,EAAE,QAAQ,GACZ,MAAM,CAYR;AAED,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,gBAAgB,EAAE,CAK1F;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,gBAAgB,EAAE,CAKvF;AAED,MAAM,WAAW,cAAe,SAAQ,OAAO;IAC7C,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE;IAAE,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAAE,EACtC,EAAE,CAAC,EAAE,QAAQ,GACZ,cAAc,EAAE,CAkClB"}
package/dist/index.js CHANGED
@@ -704,6 +704,20 @@ var MIGRATIONS = [
704
704
  INSERT OR IGNORE INTO _migrations (id) VALUES (30);
705
705
  `,
706
706
  `
707
+ CREATE TABLE IF NOT EXISTS project_agent_roles (
708
+ id TEXT PRIMARY KEY,
709
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
710
+ agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
711
+ role TEXT NOT NULL,
712
+ is_lead INTEGER NOT NULL DEFAULT 0,
713
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
714
+ UNIQUE(project_id, agent_id, role)
715
+ );
716
+ CREATE INDEX IF NOT EXISTS idx_project_agent_roles_project ON project_agent_roles(project_id);
717
+ CREATE INDEX IF NOT EXISTS idx_project_agent_roles_agent ON project_agent_roles(agent_id);
718
+ INSERT OR IGNORE INTO _migrations (id) VALUES (31);
719
+ `,
720
+ `
707
721
  CREATE TABLE IF NOT EXISTS file_locks (
708
722
  id TEXT PRIMARY KEY,
709
723
  path TEXT NOT NULL UNIQUE,
@@ -715,7 +729,7 @@ var MIGRATIONS = [
715
729
  CREATE INDEX IF NOT EXISTS idx_file_locks_path ON file_locks(path);
716
730
  CREATE INDEX IF NOT EXISTS idx_file_locks_agent ON file_locks(agent_id);
717
731
  CREATE INDEX IF NOT EXISTS idx_file_locks_expires ON file_locks(expires_at);
718
- INSERT OR IGNORE INTO _migrations (id) VALUES (31);
732
+ INSERT OR IGNORE INTO _migrations (id) VALUES (32);
719
733
  `
720
734
  ];
721
735
  var _db = null;
package/dist/mcp/index.js CHANGED
@@ -942,6 +942,20 @@ var init_database = __esm(() => {
942
942
  ALTER TABLE agents ADD COLUMN status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'archived'));
943
943
  CREATE INDEX IF NOT EXISTS idx_agents_status ON agents(status);
944
944
  INSERT OR IGNORE INTO _migrations (id) VALUES (30);
945
+ `,
946
+ `
947
+ CREATE TABLE IF NOT EXISTS project_agent_roles (
948
+ id TEXT PRIMARY KEY,
949
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
950
+ agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
951
+ role TEXT NOT NULL,
952
+ is_lead INTEGER NOT NULL DEFAULT 0,
953
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
954
+ UNIQUE(project_id, agent_id, role)
955
+ );
956
+ CREATE INDEX IF NOT EXISTS idx_project_agent_roles_project ON project_agent_roles(project_id);
957
+ CREATE INDEX IF NOT EXISTS idx_project_agent_roles_agent ON project_agent_roles(agent_id);
958
+ INSERT OR IGNORE INTO _migrations (id) VALUES (31);
945
959
  `,
946
960
  `
947
961
  CREATE TABLE IF NOT EXISTS file_locks (
@@ -955,7 +969,7 @@ var init_database = __esm(() => {
955
969
  CREATE INDEX IF NOT EXISTS idx_file_locks_path ON file_locks(path);
956
970
  CREATE INDEX IF NOT EXISTS idx_file_locks_agent ON file_locks(agent_id);
957
971
  CREATE INDEX IF NOT EXISTS idx_file_locks_expires ON file_locks(expires_at);
958
- INSERT OR IGNORE INTO _migrations (id) VALUES (31);
972
+ INSERT OR IGNORE INTO _migrations (id) VALUES (32);
959
973
  `
960
974
  ];
961
975
  });
@@ -1977,6 +1991,81 @@ var init_kg = __esm(() => {
1977
1991
  init_database();
1978
1992
  });
1979
1993
 
1994
+ // src/db/project-agent-roles.ts
1995
+ var exports_project_agent_roles = {};
1996
+ __export(exports_project_agent_roles, {
1997
+ setProjectAgentRole: () => setProjectAgentRole,
1998
+ removeProjectAgentRole: () => removeProjectAgentRole,
1999
+ listProjectAgentRoles: () => listProjectAgentRoles,
2000
+ getProjectOrgChart: () => getProjectOrgChart,
2001
+ getAgentProjectRoles: () => getAgentProjectRoles
2002
+ });
2003
+ function rowToRole(row) {
2004
+ return { ...row, is_lead: row.is_lead === 1 };
2005
+ }
2006
+ function setProjectAgentRole(projectId, agentId, role, isLead = false, db) {
2007
+ const d = db || getDatabase();
2008
+ const existing = d.query("SELECT * FROM project_agent_roles WHERE project_id = ? AND agent_id = ? AND role = ?").get(projectId, agentId, role);
2009
+ if (existing) {
2010
+ d.run("UPDATE project_agent_roles SET is_lead = ? WHERE id = ?", [isLead ? 1 : 0, existing.id]);
2011
+ return rowToRole(d.query("SELECT * FROM project_agent_roles WHERE id = ?").get(existing.id));
2012
+ }
2013
+ const id = uuid();
2014
+ d.run("INSERT INTO project_agent_roles (id, project_id, agent_id, role, is_lead, created_at) VALUES (?, ?, ?, ?, ?, ?)", [id, projectId, agentId, role, isLead ? 1 : 0, now()]);
2015
+ return rowToRole(d.query("SELECT * FROM project_agent_roles WHERE id = ?").get(id));
2016
+ }
2017
+ function removeProjectAgentRole(projectId, agentId, role, db) {
2018
+ const d = db || getDatabase();
2019
+ if (role) {
2020
+ return d.run("DELETE FROM project_agent_roles WHERE project_id = ? AND agent_id = ? AND role = ?", [projectId, agentId, role]).changes;
2021
+ }
2022
+ return d.run("DELETE FROM project_agent_roles WHERE project_id = ? AND agent_id = ?", [projectId, agentId]).changes;
2023
+ }
2024
+ function listProjectAgentRoles(projectId, db) {
2025
+ const d = db || getDatabase();
2026
+ return d.query("SELECT * FROM project_agent_roles WHERE project_id = ? ORDER BY role, created_at").all(projectId).map(rowToRole);
2027
+ }
2028
+ function getAgentProjectRoles(agentId, db) {
2029
+ const d = db || getDatabase();
2030
+ return d.query("SELECT * FROM project_agent_roles WHERE agent_id = ? ORDER BY project_id, role").all(agentId).map(rowToRole);
2031
+ }
2032
+ function getProjectOrgChart(projectId, opts, db) {
2033
+ const d = db || getDatabase();
2034
+ const globalTree = getOrgChart(d);
2035
+ const projectRoles = listProjectAgentRoles(projectId, d);
2036
+ const rolesByAgent = new Map;
2037
+ for (const pr of projectRoles) {
2038
+ if (!rolesByAgent.has(pr.agent_id))
2039
+ rolesByAgent.set(pr.agent_id, { roles: [], isLead: false });
2040
+ const entry = rolesByAgent.get(pr.agent_id);
2041
+ entry.roles.push(pr.role);
2042
+ if (pr.is_lead)
2043
+ entry.isLead = true;
2044
+ }
2045
+ function augmentTree(nodes) {
2046
+ return nodes.map((n) => {
2047
+ const override = rolesByAgent.get(n.agent.id);
2048
+ return {
2049
+ ...n,
2050
+ reports: augmentTree(n.reports),
2051
+ project_roles: override?.roles ?? [],
2052
+ is_project_lead: override?.isLead ?? false
2053
+ };
2054
+ }).filter((n) => {
2055
+ if (!opts?.filter_to_project)
2056
+ return true;
2057
+ const hasRole = n.project_roles.length > 0;
2058
+ const hasDescendant = n.reports.length > 0;
2059
+ return hasRole || hasDescendant;
2060
+ });
2061
+ }
2062
+ return augmentTree(globalTree);
2063
+ }
2064
+ var init_project_agent_roles = __esm(() => {
2065
+ init_database();
2066
+ init_agents();
2067
+ });
2068
+
1980
2069
  // src/db/patrol.ts
1981
2070
  var exports_patrol = {};
1982
2071
  __export(exports_patrol, {
@@ -11496,6 +11585,78 @@ ${lines.join(`
11496
11585
  }
11497
11586
  });
11498
11587
  }
11588
+ if (shouldRegisterTool("set_project_agent_role")) {
11589
+ server.tool("set_project_agent_role", "Assign an agent a role on a specific project (client, lead, developer, qa, reviewer, etc.). Per-project roles extend the global org chart.", {
11590
+ project_id: exports_external.string().describe("Project ID"),
11591
+ agent_name: exports_external.string().describe("Agent name"),
11592
+ role: exports_external.string().describe("Role on this project (e.g. 'lead', 'developer', 'qa')"),
11593
+ is_lead: exports_external.coerce.boolean().optional().describe("Whether this agent is the project lead for this role")
11594
+ }, async ({ project_id, agent_name, role, is_lead }) => {
11595
+ try {
11596
+ const { setProjectAgentRole: setProjectAgentRole2 } = (init_project_agent_roles(), __toCommonJS(exports_project_agent_roles));
11597
+ const agent = getAgentByName(agent_name);
11598
+ if (!agent)
11599
+ return { content: [{ type: "text", text: `Agent not found: ${agent_name}` }], isError: true };
11600
+ const pid = resolveId(project_id, "projects");
11601
+ const result = setProjectAgentRole2(pid, agent.id, role, is_lead ?? false);
11602
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
11603
+ } catch (e) {
11604
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
11605
+ }
11606
+ });
11607
+ }
11608
+ if (shouldRegisterTool("get_project_org_chart")) {
11609
+ server.tool("get_project_org_chart", "Get org chart scoped to a project \u2014 global hierarchy with per-project role overrides merged in.", {
11610
+ project_id: exports_external.string().describe("Project ID"),
11611
+ format: exports_external.enum(["text", "json"]).optional().describe("Output format (default: text)"),
11612
+ filter_to_project: exports_external.coerce.boolean().optional().describe("Only show agents with a role on this project")
11613
+ }, async ({ project_id, format, filter_to_project }) => {
11614
+ try {
11615
+ let render = function(nodes, indent = 0) {
11616
+ return nodes.map((n) => {
11617
+ const prefix = " ".repeat(indent);
11618
+ const title = n.agent.title ? ` \u2014 ${n.agent.title}` : "";
11619
+ const globalRole = n.agent.role ? ` [${n.agent.role}]` : "";
11620
+ const projectRoles = n.project_roles.length > 0 ? ` <${n.project_roles.join(", ")}>` : "";
11621
+ const lead = n.is_project_lead ? " \u2605" : "";
11622
+ const lastSeen = new Date(n.agent.last_seen_at).getTime();
11623
+ const active = now2 - lastSeen < ACTIVE_MS ? " \u25CF" : " \u25CB";
11624
+ const line = `${prefix}${active} ${n.agent.name}${title}${globalRole}${projectRoles}${lead}`;
11625
+ const children = n.reports.length > 0 ? `
11626
+ ` + render(n.reports, indent + 1) : "";
11627
+ return line + children;
11628
+ }).join(`
11629
+ `);
11630
+ };
11631
+ const { getProjectOrgChart: getProjectOrgChart2 } = (init_project_agent_roles(), __toCommonJS(exports_project_agent_roles));
11632
+ const pid = resolveId(project_id, "projects");
11633
+ const tree = getProjectOrgChart2(pid, { filter_to_project });
11634
+ if (format === "json") {
11635
+ return { content: [{ type: "text", text: JSON.stringify(tree, null, 2) }] };
11636
+ }
11637
+ const now2 = Date.now();
11638
+ const ACTIVE_MS = 30 * 60 * 1000;
11639
+ const text = tree.length > 0 ? render(tree) : "No agents in this project's org chart.";
11640
+ return { content: [{ type: "text", text }] };
11641
+ } catch (e) {
11642
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
11643
+ }
11644
+ });
11645
+ }
11646
+ if (shouldRegisterTool("list_project_agent_roles")) {
11647
+ server.tool("list_project_agent_roles", "List all agent role assignments for a project.", {
11648
+ project_id: exports_external.string().describe("Project ID")
11649
+ }, async ({ project_id }) => {
11650
+ try {
11651
+ const { listProjectAgentRoles: listProjectAgentRoles2 } = (init_project_agent_roles(), __toCommonJS(exports_project_agent_roles));
11652
+ const pid = resolveId(project_id, "projects");
11653
+ const roles = listProjectAgentRoles2(pid);
11654
+ return { content: [{ type: "text", text: JSON.stringify(roles, null, 2) }] };
11655
+ } catch (e) {
11656
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
11657
+ }
11658
+ });
11659
+ }
11499
11660
  if (shouldRegisterTool("get_capable_agents")) {
11500
11661
  server.tool("get_capable_agents", "Find agents that match given capabilities, sorted by match score.", {
11501
11662
  capabilities: exports_external.array(exports_external.string()).describe("Required capabilities to match against"),
@@ -858,6 +858,20 @@ var init_database = __esm(() => {
858
858
  ALTER TABLE agents ADD COLUMN status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'archived'));
859
859
  CREATE INDEX IF NOT EXISTS idx_agents_status ON agents(status);
860
860
  INSERT OR IGNORE INTO _migrations (id) VALUES (30);
861
+ `,
862
+ `
863
+ CREATE TABLE IF NOT EXISTS project_agent_roles (
864
+ id TEXT PRIMARY KEY,
865
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
866
+ agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
867
+ role TEXT NOT NULL,
868
+ is_lead INTEGER NOT NULL DEFAULT 0,
869
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
870
+ UNIQUE(project_id, agent_id, role)
871
+ );
872
+ CREATE INDEX IF NOT EXISTS idx_project_agent_roles_project ON project_agent_roles(project_id);
873
+ CREATE INDEX IF NOT EXISTS idx_project_agent_roles_agent ON project_agent_roles(agent_id);
874
+ INSERT OR IGNORE INTO _migrations (id) VALUES (31);
861
875
  `,
862
876
  `
863
877
  CREATE TABLE IF NOT EXISTS file_locks (
@@ -871,7 +885,7 @@ var init_database = __esm(() => {
871
885
  CREATE INDEX IF NOT EXISTS idx_file_locks_path ON file_locks(path);
872
886
  CREATE INDEX IF NOT EXISTS idx_file_locks_agent ON file_locks(agent_id);
873
887
  CREATE INDEX IF NOT EXISTS idx_file_locks_expires ON file_locks(expires_at);
874
- INSERT OR IGNORE INTO _migrations (id) VALUES (31);
888
+ INSERT OR IGNORE INTO _migrations (id) VALUES (32);
875
889
  `
876
890
  ];
877
891
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/todos",
3
- "version": "0.10.11",
3
+ "version": "0.10.13",
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",