@hasna/todos 0.9.82 → 0.10.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.
package/dist/cli/index.js CHANGED
@@ -2282,6 +2282,30 @@ function ensureSchema(db) {
2282
2282
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
2283
2283
  updated_at TEXT NOT NULL DEFAULT (datetime('now'))
2284
2284
  )`);
2285
+ ensureTable("task_relationships", `
2286
+ CREATE TABLE task_relationships (
2287
+ id TEXT PRIMARY KEY,
2288
+ source_task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
2289
+ target_task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
2290
+ relationship_type TEXT NOT NULL,
2291
+ metadata TEXT DEFAULT '{}',
2292
+ created_by TEXT,
2293
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
2294
+ CHECK (source_task_id != target_task_id)
2295
+ )`);
2296
+ ensureTable("kg_edges", `
2297
+ CREATE TABLE kg_edges (
2298
+ id TEXT PRIMARY KEY,
2299
+ source_id TEXT NOT NULL,
2300
+ source_type TEXT NOT NULL,
2301
+ target_id TEXT NOT NULL,
2302
+ target_type TEXT NOT NULL,
2303
+ relation_type TEXT NOT NULL,
2304
+ weight REAL NOT NULL DEFAULT 1.0,
2305
+ metadata TEXT DEFAULT '{}',
2306
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
2307
+ UNIQUE(source_id, source_type, target_id, target_type, relation_type)
2308
+ )`);
2285
2309
  ensureColumn("projects", "task_list_id", "TEXT");
2286
2310
  ensureColumn("projects", "task_prefix", "TEXT");
2287
2311
  ensureColumn("projects", "task_counter", "INTEGER NOT NULL DEFAULT 0");
@@ -2306,6 +2330,7 @@ function ensureSchema(db) {
2306
2330
  ensureColumn("agents", "title", "TEXT");
2307
2331
  ensureColumn("agents", "level", "TEXT");
2308
2332
  ensureColumn("agents", "org_id", "TEXT");
2333
+ ensureColumn("agents", "capabilities", "TEXT DEFAULT '[]'");
2309
2334
  ensureColumn("projects", "org_id", "TEXT");
2310
2335
  ensureColumn("plans", "task_list_id", "TEXT");
2311
2336
  ensureColumn("plans", "agent_id", "TEXT");
@@ -2332,6 +2357,12 @@ function ensureSchema(db) {
2332
2357
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_sources_project ON project_sources(project_id)");
2333
2358
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_sources_type ON project_sources(type)");
2334
2359
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_assigned_by ON tasks(assigned_by)");
2360
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_rel_source ON task_relationships(source_task_id)");
2361
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_rel_target ON task_relationships(target_task_id)");
2362
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_rel_type ON task_relationships(relationship_type)");
2363
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_kg_source ON kg_edges(source_id, source_type)");
2364
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_kg_target ON kg_edges(target_id, target_type)");
2365
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_kg_relation ON kg_edges(relation_type)");
2335
2366
  }
2336
2367
  function backfillTaskTags(db) {
2337
2368
  try {
@@ -2801,6 +2832,44 @@ var init_database = __esm(() => {
2801
2832
  ALTER TABLE tasks ADD COLUMN assigned_from_project TEXT;
2802
2833
  CREATE INDEX IF NOT EXISTS idx_tasks_assigned_by ON tasks(assigned_by);
2803
2834
  INSERT OR IGNORE INTO _migrations (id) VALUES (26);
2835
+ `,
2836
+ `
2837
+ CREATE TABLE IF NOT EXISTS task_relationships (
2838
+ id TEXT PRIMARY KEY,
2839
+ source_task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
2840
+ target_task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
2841
+ relationship_type TEXT NOT NULL CHECK(relationship_type IN ('related_to', 'conflicts_with', 'similar_to', 'duplicates', 'supersedes', 'modifies_same_file')),
2842
+ metadata TEXT DEFAULT '{}',
2843
+ created_by TEXT,
2844
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
2845
+ CHECK (source_task_id != target_task_id)
2846
+ );
2847
+ CREATE INDEX IF NOT EXISTS idx_task_rel_source ON task_relationships(source_task_id);
2848
+ CREATE INDEX IF NOT EXISTS idx_task_rel_target ON task_relationships(target_task_id);
2849
+ CREATE INDEX IF NOT EXISTS idx_task_rel_type ON task_relationships(relationship_type);
2850
+ INSERT OR IGNORE INTO _migrations (id) VALUES (27);
2851
+ `,
2852
+ `
2853
+ CREATE TABLE IF NOT EXISTS kg_edges (
2854
+ id TEXT PRIMARY KEY,
2855
+ source_id TEXT NOT NULL,
2856
+ source_type TEXT NOT NULL,
2857
+ target_id TEXT NOT NULL,
2858
+ target_type TEXT NOT NULL,
2859
+ relation_type TEXT NOT NULL,
2860
+ weight REAL NOT NULL DEFAULT 1.0,
2861
+ metadata TEXT DEFAULT '{}',
2862
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
2863
+ UNIQUE(source_id, source_type, target_id, target_type, relation_type)
2864
+ );
2865
+ CREATE INDEX IF NOT EXISTS idx_kg_source ON kg_edges(source_id, source_type);
2866
+ CREATE INDEX IF NOT EXISTS idx_kg_target ON kg_edges(target_id, target_type);
2867
+ CREATE INDEX IF NOT EXISTS idx_kg_relation ON kg_edges(relation_type);
2868
+ INSERT OR IGNORE INTO _migrations (id) VALUES (28);
2869
+ `,
2870
+ `
2871
+ ALTER TABLE agents ADD COLUMN capabilities TEXT DEFAULT '[]';
2872
+ INSERT OR IGNORE INTO _migrations (id) VALUES (29);
2804
2873
  `
2805
2874
  ];
2806
2875
  });
@@ -3136,7 +3205,9 @@ __export(exports_config, {
3136
3205
  getSyncAgentsFromConfig: () => getSyncAgentsFromConfig,
3137
3206
  getCompletionGuardConfig: () => getCompletionGuardConfig,
3138
3207
  getAgentTasksDir: () => getAgentTasksDir,
3139
- getAgentTaskListId: () => getAgentTaskListId
3208
+ getAgentTaskListId: () => getAgentTaskListId,
3209
+ getAgentPoolForProject: () => getAgentPoolForProject,
3210
+ DEFAULT_AGENT_POOL: () => DEFAULT_AGENT_POOL
3140
3211
  });
3141
3212
  import { existsSync as existsSync3 } from "fs";
3142
3213
  import { join as join3 } from "path";
@@ -3181,6 +3252,23 @@ function getTaskPrefixConfig() {
3181
3252
  const config = loadConfig();
3182
3253
  return config.task_prefix || null;
3183
3254
  }
3255
+ function getAgentPoolForProject(workingDir) {
3256
+ const config = loadConfig();
3257
+ if (workingDir && config.project_pools) {
3258
+ let bestKey = null;
3259
+ let bestLen = 0;
3260
+ for (const key of Object.keys(config.project_pools)) {
3261
+ if (workingDir.startsWith(key) && key.length > bestLen) {
3262
+ bestKey = key;
3263
+ bestLen = key.length;
3264
+ }
3265
+ }
3266
+ if (bestKey && config.project_pools[bestKey]) {
3267
+ return config.project_pools[bestKey];
3268
+ }
3269
+ }
3270
+ return config.agent_pool || DEFAULT_AGENT_POOL;
3271
+ }
3184
3272
  function getCompletionGuardConfig(projectPath) {
3185
3273
  const config = loadConfig();
3186
3274
  const global = { ...GUARD_DEFAULTS, ...config.completion_guard };
@@ -3189,9 +3277,26 @@ function getCompletionGuardConfig(projectPath) {
3189
3277
  }
3190
3278
  return global;
3191
3279
  }
3192
- var cached = null, GUARD_DEFAULTS;
3280
+ var DEFAULT_AGENT_POOL, cached = null, GUARD_DEFAULTS;
3193
3281
  var init_config = __esm(() => {
3194
3282
  init_sync_utils();
3283
+ DEFAULT_AGENT_POOL = [
3284
+ "maximus",
3285
+ "cassius",
3286
+ "aurelius",
3287
+ "brutus",
3288
+ "titus",
3289
+ "nero",
3290
+ "cicero",
3291
+ "seneca",
3292
+ "cato",
3293
+ "julius",
3294
+ "marcus",
3295
+ "lucius",
3296
+ "quintus",
3297
+ "gaius",
3298
+ "publius"
3299
+ ];
3195
3300
  GUARD_DEFAULTS = {
3196
3301
  enabled: false,
3197
3302
  min_work_seconds: 30,
@@ -4643,14 +4748,22 @@ __export(exports_agents, {
4643
4748
  updateAgentActivity: () => updateAgentActivity,
4644
4749
  updateAgent: () => updateAgent,
4645
4750
  registerAgent: () => registerAgent,
4751
+ matchCapabilities: () => matchCapabilities,
4646
4752
  listAgents: () => listAgents,
4647
4753
  isAgentConflict: () => isAgentConflict,
4648
4754
  getOrgChart: () => getOrgChart,
4649
4755
  getDirectReports: () => getDirectReports,
4756
+ getCapableAgents: () => getCapableAgents,
4757
+ getAvailableNamesFromPool: () => getAvailableNamesFromPool,
4650
4758
  getAgentByName: () => getAgentByName,
4651
4759
  getAgent: () => getAgent,
4652
4760
  deleteAgent: () => deleteAgent
4653
4761
  });
4762
+ function getAvailableNamesFromPool(pool, db) {
4763
+ const cutoff = new Date(Date.now() - AGENT_ACTIVE_WINDOW_MS).toISOString();
4764
+ const activeNames = new Set(db.query("SELECT name FROM agents WHERE last_seen_at > ?").all(cutoff).map((r) => r.name.toLowerCase()));
4765
+ return pool.filter((name) => !activeNames.has(name.toLowerCase()));
4766
+ }
4654
4767
  function shortUuid() {
4655
4768
  return crypto.randomUUID().slice(0, 8);
4656
4769
  }
@@ -4658,12 +4771,31 @@ function rowToAgent(row) {
4658
4771
  return {
4659
4772
  ...row,
4660
4773
  permissions: JSON.parse(row.permissions || '["*"]'),
4774
+ capabilities: JSON.parse(row.capabilities || "[]"),
4661
4775
  metadata: JSON.parse(row.metadata || "{}")
4662
4776
  };
4663
4777
  }
4664
4778
  function registerAgent(input, db) {
4665
4779
  const d = db || getDatabase();
4666
4780
  const normalizedName = input.name.trim().toLowerCase();
4781
+ if (input.pool && input.pool.length > 0) {
4782
+ const poolLower = input.pool.map((n) => n.toLowerCase());
4783
+ if (!poolLower.includes(normalizedName)) {
4784
+ const available = getAvailableNamesFromPool(input.pool, d);
4785
+ const suggestion = available.length > 0 ? available[0] : null;
4786
+ return {
4787
+ conflict: true,
4788
+ pool_violation: true,
4789
+ existing_id: "",
4790
+ existing_name: normalizedName,
4791
+ last_seen_at: "",
4792
+ session_hint: null,
4793
+ working_dir: input.working_dir || null,
4794
+ suggestions: available.slice(0, 5),
4795
+ message: `"${normalizedName}" is not in this project's agent pool [${input.pool.join(", ")}]. ${available.length > 0 ? `Try: ${available.slice(0, 3).join(", ")}` : "No names are currently available \u2014 wait for an active agent to go stale."}${suggestion ? ` Suggested: ${suggestion}` : ""}`
4796
+ };
4797
+ }
4798
+ }
4667
4799
  const existing = getAgentByName(normalizedName, d);
4668
4800
  if (existing) {
4669
4801
  const lastSeenMs = new Date(existing.last_seen_at).getTime();
@@ -4672,6 +4804,7 @@ function registerAgent(input, db) {
4672
4804
  const differentSession = input.session_id && existing.session_id && input.session_id !== existing.session_id;
4673
4805
  if (isActive && differentSession) {
4674
4806
  const minutesAgo = Math.round((Date.now() - lastSeenMs) / 60000);
4807
+ const suggestions = input.pool ? getAvailableNamesFromPool(input.pool, d) : [];
4675
4808
  return {
4676
4809
  conflict: true,
4677
4810
  existing_id: existing.id,
@@ -4679,7 +4812,8 @@ function registerAgent(input, db) {
4679
4812
  last_seen_at: existing.last_seen_at,
4680
4813
  session_hint: existing.session_id ? existing.session_id.slice(0, 8) : null,
4681
4814
  working_dir: existing.working_dir,
4682
- message: `Agent "${normalizedName}" is already active (last seen ${minutesAgo}m ago, session ${existing.session_id?.slice(0, 8)}\u2026, dir: ${existing.working_dir ?? "unknown"}). Are you that agent? If so, pass session_id="${existing.session_id}" to reclaim it. Otherwise choose a different name.`
4815
+ suggestions: suggestions.slice(0, 5),
4816
+ message: `Agent "${normalizedName}" is already active (last seen ${minutesAgo}m ago, session ${existing.session_id?.slice(0, 8)}\u2026, dir: ${existing.working_dir ?? "unknown"}). Are you that agent? If so, pass session_id="${existing.session_id}" to reclaim it. Otherwise choose a different name.${suggestions.length > 0 ? ` Available: ${suggestions.slice(0, 3).join(", ")}` : ""}`
4683
4817
  };
4684
4818
  }
4685
4819
  const updates = ["last_seen_at = ?"];
@@ -4702,8 +4836,8 @@ function registerAgent(input, db) {
4702
4836
  }
4703
4837
  const id = shortUuid();
4704
4838
  const timestamp = now();
4705
- d.run(`INSERT INTO agents (id, name, description, role, title, level, permissions, reports_to, org_id, metadata, created_at, last_seen_at, session_id, working_dir)
4706
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
4839
+ d.run(`INSERT INTO agents (id, name, description, role, title, level, permissions, capabilities, reports_to, org_id, metadata, created_at, last_seen_at, session_id, working_dir)
4840
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
4707
4841
  id,
4708
4842
  normalizedName,
4709
4843
  input.description || null,
@@ -4711,6 +4845,7 @@ function registerAgent(input, db) {
4711
4845
  input.title || null,
4712
4846
  input.level || null,
4713
4847
  JSON.stringify(input.permissions || ["*"]),
4848
+ JSON.stringify(input.capabilities || []),
4714
4849
  input.reports_to || null,
4715
4850
  input.org_id || null,
4716
4851
  JSON.stringify(input.metadata || {}),
@@ -4766,6 +4901,10 @@ function updateAgent(id, input, db) {
4766
4901
  sets.push("permissions = ?");
4767
4902
  params.push(JSON.stringify(input.permissions));
4768
4903
  }
4904
+ if (input.capabilities !== undefined) {
4905
+ sets.push("capabilities = ?");
4906
+ params.push(JSON.stringify(input.capabilities));
4907
+ }
4769
4908
  if (input.title !== undefined) {
4770
4909
  sets.push("title = ?");
4771
4910
  params.push(input.title);
@@ -4813,6 +4952,28 @@ function getOrgChart(db) {
4813
4952
  }
4814
4953
  return buildTree(null);
4815
4954
  }
4955
+ function matchCapabilities(agentCapabilities, requiredCapabilities) {
4956
+ if (requiredCapabilities.length === 0)
4957
+ return 1;
4958
+ if (agentCapabilities.length === 0)
4959
+ return 0;
4960
+ const agentSet = new Set(agentCapabilities.map((c) => c.toLowerCase()));
4961
+ let matches = 0;
4962
+ for (const req of requiredCapabilities) {
4963
+ if (agentSet.has(req.toLowerCase()))
4964
+ matches++;
4965
+ }
4966
+ return matches / requiredCapabilities.length;
4967
+ }
4968
+ function getCapableAgents(capabilities, opts, db) {
4969
+ const agents = listAgents(db);
4970
+ const minScore = opts?.min_score ?? 0.1;
4971
+ const scored = agents.map((agent) => ({
4972
+ agent,
4973
+ score: matchCapabilities(agent.capabilities, capabilities)
4974
+ })).filter((entry) => entry.score >= minScore).sort((a, b) => b.score - a.score);
4975
+ return opts?.limit ? scored.slice(0, opts.limit) : scored;
4976
+ }
4816
4977
  var AGENT_ACTIVE_WINDOW_MS;
4817
4978
  var init_agents = __esm(() => {
4818
4979
  init_database();
@@ -9748,6 +9909,584 @@ var init_handoffs = __esm(() => {
9748
9909
  init_database();
9749
9910
  });
9750
9911
 
9912
+ // src/db/task-relationships.ts
9913
+ var exports_task_relationships = {};
9914
+ __export(exports_task_relationships, {
9915
+ removeTaskRelationshipByPair: () => removeTaskRelationshipByPair,
9916
+ removeTaskRelationship: () => removeTaskRelationship,
9917
+ getTaskRelationships: () => getTaskRelationships,
9918
+ getTaskRelationship: () => getTaskRelationship,
9919
+ findRelatedTaskIds: () => findRelatedTaskIds,
9920
+ autoDetectFileRelationships: () => autoDetectFileRelationships,
9921
+ addTaskRelationship: () => addTaskRelationship,
9922
+ RELATIONSHIP_TYPES: () => RELATIONSHIP_TYPES
9923
+ });
9924
+ function rowToRelationship(row) {
9925
+ return {
9926
+ ...row,
9927
+ relationship_type: row.relationship_type,
9928
+ metadata: JSON.parse(row.metadata || "{}")
9929
+ };
9930
+ }
9931
+ function addTaskRelationship(input, db) {
9932
+ const d = db || getDatabase();
9933
+ const id = uuid();
9934
+ const timestamp = now();
9935
+ if (input.source_task_id === input.target_task_id) {
9936
+ throw new Error("Cannot create a relationship between a task and itself");
9937
+ }
9938
+ const symmetric = ["related_to", "conflicts_with", "similar_to", "modifies_same_file"];
9939
+ if (symmetric.includes(input.relationship_type)) {
9940
+ const existing = d.query(`SELECT id FROM task_relationships
9941
+ WHERE relationship_type = ?
9942
+ AND ((source_task_id = ? AND target_task_id = ?) OR (source_task_id = ? AND target_task_id = ?))`).get(input.relationship_type, input.source_task_id, input.target_task_id, input.target_task_id, input.source_task_id);
9943
+ if (existing) {
9944
+ return getTaskRelationship(existing.id, d);
9945
+ }
9946
+ } else {
9947
+ const existing = d.query("SELECT id FROM task_relationships WHERE source_task_id = ? AND target_task_id = ? AND relationship_type = ?").get(input.source_task_id, input.target_task_id, input.relationship_type);
9948
+ if (existing) {
9949
+ return getTaskRelationship(existing.id, d);
9950
+ }
9951
+ }
9952
+ d.run(`INSERT INTO task_relationships (id, source_task_id, target_task_id, relationship_type, metadata, created_by, created_at)
9953
+ VALUES (?, ?, ?, ?, ?, ?, ?)`, [
9954
+ id,
9955
+ input.source_task_id,
9956
+ input.target_task_id,
9957
+ input.relationship_type,
9958
+ JSON.stringify(input.metadata || {}),
9959
+ input.created_by || null,
9960
+ timestamp
9961
+ ]);
9962
+ return getTaskRelationship(id, d);
9963
+ }
9964
+ function getTaskRelationship(id, db) {
9965
+ const d = db || getDatabase();
9966
+ const row = d.query("SELECT * FROM task_relationships WHERE id = ?").get(id);
9967
+ return row ? rowToRelationship(row) : null;
9968
+ }
9969
+ function removeTaskRelationship(id, db) {
9970
+ const d = db || getDatabase();
9971
+ return d.run("DELETE FROM task_relationships WHERE id = ?", [id]).changes > 0;
9972
+ }
9973
+ function removeTaskRelationshipByPair(sourceTaskId, targetTaskId, relationshipType, db) {
9974
+ const d = db || getDatabase();
9975
+ const symmetric = ["related_to", "conflicts_with", "similar_to", "modifies_same_file"];
9976
+ if (symmetric.includes(relationshipType)) {
9977
+ return d.run(`DELETE FROM task_relationships
9978
+ WHERE relationship_type = ?
9979
+ AND ((source_task_id = ? AND target_task_id = ?) OR (source_task_id = ? AND target_task_id = ?))`, [relationshipType, sourceTaskId, targetTaskId, targetTaskId, sourceTaskId]).changes > 0;
9980
+ }
9981
+ return d.run("DELETE FROM task_relationships WHERE source_task_id = ? AND target_task_id = ? AND relationship_type = ?", [sourceTaskId, targetTaskId, relationshipType]).changes > 0;
9982
+ }
9983
+ function getTaskRelationships(taskId, relationshipType, db) {
9984
+ const d = db || getDatabase();
9985
+ let sql = "SELECT * FROM task_relationships WHERE (source_task_id = ? OR target_task_id = ?)";
9986
+ const params = [taskId, taskId];
9987
+ if (relationshipType) {
9988
+ sql += " AND relationship_type = ?";
9989
+ params.push(relationshipType);
9990
+ }
9991
+ sql += " ORDER BY created_at DESC";
9992
+ return d.query(sql).all(...params).map(rowToRelationship);
9993
+ }
9994
+ function findRelatedTaskIds(taskId, relationshipType, db) {
9995
+ const rels = getTaskRelationships(taskId, relationshipType, db);
9996
+ const ids = new Set;
9997
+ for (const rel of rels) {
9998
+ if (rel.source_task_id === taskId)
9999
+ ids.add(rel.target_task_id);
10000
+ else
10001
+ ids.add(rel.source_task_id);
10002
+ }
10003
+ return [...ids];
10004
+ }
10005
+ function autoDetectFileRelationships(taskId, db) {
10006
+ const d = db || getDatabase();
10007
+ const files = d.query("SELECT path FROM task_files WHERE task_id = ? AND status != 'removed'").all(taskId);
10008
+ const created = [];
10009
+ for (const file of files) {
10010
+ const others = d.query("SELECT DISTINCT task_id FROM task_files WHERE path = ? AND task_id != ? AND status != 'removed'").all(file.path, taskId);
10011
+ for (const other of others) {
10012
+ try {
10013
+ const rel = addTaskRelationship({
10014
+ source_task_id: taskId,
10015
+ target_task_id: other.task_id,
10016
+ relationship_type: "modifies_same_file",
10017
+ metadata: { shared_file: file.path }
10018
+ }, d);
10019
+ created.push(rel);
10020
+ } catch {}
10021
+ }
10022
+ }
10023
+ return created;
10024
+ }
10025
+ var RELATIONSHIP_TYPES;
10026
+ var init_task_relationships = __esm(() => {
10027
+ init_database();
10028
+ RELATIONSHIP_TYPES = [
10029
+ "related_to",
10030
+ "conflicts_with",
10031
+ "similar_to",
10032
+ "duplicates",
10033
+ "supersedes",
10034
+ "modifies_same_file"
10035
+ ];
10036
+ });
10037
+
10038
+ // src/db/kg.ts
10039
+ var exports_kg = {};
10040
+ __export(exports_kg, {
10041
+ syncKgEdges: () => syncKgEdges,
10042
+ removeKgEdges: () => removeKgEdges,
10043
+ getRelated: () => getRelated,
10044
+ getImpactAnalysis: () => getImpactAnalysis,
10045
+ getCriticalPath: () => getCriticalPath,
10046
+ findPath: () => findPath,
10047
+ addKgEdge: () => addKgEdge
10048
+ });
10049
+ function rowToEdge(row) {
10050
+ return {
10051
+ ...row,
10052
+ metadata: JSON.parse(row.metadata || "{}")
10053
+ };
10054
+ }
10055
+ function syncKgEdges(db) {
10056
+ const d = db || getDatabase();
10057
+ let synced = 0;
10058
+ const tx = d.transaction(() => {
10059
+ const deps = d.query("SELECT task_id, depends_on FROM task_dependencies").all();
10060
+ for (const dep of deps) {
10061
+ synced += upsertEdge(d, dep.task_id, "task", dep.depends_on, "task", "depends_on");
10062
+ }
10063
+ const assignments = d.query("SELECT id, assigned_to FROM tasks WHERE assigned_to IS NOT NULL").all();
10064
+ for (const a of assignments) {
10065
+ synced += upsertEdge(d, a.id, "task", a.assigned_to, "agent", "assigned_to");
10066
+ }
10067
+ const agents = d.query("SELECT id, reports_to FROM agents WHERE reports_to IS NOT NULL").all();
10068
+ for (const a of agents) {
10069
+ synced += upsertEdge(d, a.id, "agent", a.reports_to, "agent", "reports_to");
10070
+ }
10071
+ const files = d.query("SELECT task_id, path FROM task_files WHERE status != 'removed'").all();
10072
+ for (const f of files) {
10073
+ synced += upsertEdge(d, f.task_id, "task", f.path, "file", "references_file");
10074
+ }
10075
+ const taskProjects = d.query("SELECT id, project_id FROM tasks WHERE project_id IS NOT NULL").all();
10076
+ for (const tp of taskProjects) {
10077
+ synced += upsertEdge(d, tp.id, "task", tp.project_id, "project", "in_project");
10078
+ }
10079
+ const taskPlans = d.query("SELECT id, plan_id FROM tasks WHERE plan_id IS NOT NULL").all();
10080
+ for (const tp of taskPlans) {
10081
+ synced += upsertEdge(d, tp.id, "task", tp.plan_id, "plan", "in_plan");
10082
+ }
10083
+ try {
10084
+ const rels = d.query("SELECT source_task_id, target_task_id, relationship_type FROM task_relationships").all();
10085
+ for (const r of rels) {
10086
+ synced += upsertEdge(d, r.source_task_id, "task", r.target_task_id, "task", r.relationship_type);
10087
+ }
10088
+ } catch {}
10089
+ });
10090
+ tx();
10091
+ return { synced };
10092
+ }
10093
+ function upsertEdge(d, sourceId, sourceType, targetId, targetType, relationType, weight = 1) {
10094
+ try {
10095
+ d.run(`INSERT OR IGNORE INTO kg_edges (id, source_id, source_type, target_id, target_type, relation_type, weight, metadata, created_at)
10096
+ VALUES (?, ?, ?, ?, ?, ?, ?, '{}', ?)`, [uuid(), sourceId, sourceType, targetId, targetType, relationType, weight, now()]);
10097
+ return 1;
10098
+ } catch {
10099
+ return 0;
10100
+ }
10101
+ }
10102
+ function getRelated(entityId, opts, db) {
10103
+ const d = db || getDatabase();
10104
+ const direction = opts?.direction || "both";
10105
+ const conditions = [];
10106
+ const params = [];
10107
+ if (direction === "outgoing" || direction === "both") {
10108
+ conditions.push("source_id = ?");
10109
+ params.push(entityId);
10110
+ }
10111
+ if (direction === "incoming" || direction === "both") {
10112
+ conditions.push("target_id = ?");
10113
+ params.push(entityId);
10114
+ }
10115
+ let sql = `SELECT * FROM kg_edges WHERE (${conditions.join(" OR ")})`;
10116
+ if (opts?.relation_type) {
10117
+ sql += " AND relation_type = ?";
10118
+ params.push(opts.relation_type);
10119
+ }
10120
+ if (opts?.entity_type) {
10121
+ sql += " AND (source_type = ? OR target_type = ?)";
10122
+ params.push(opts.entity_type, opts.entity_type);
10123
+ }
10124
+ sql += " ORDER BY weight DESC, created_at DESC";
10125
+ if (opts?.limit) {
10126
+ sql += " LIMIT ?";
10127
+ params.push(opts.limit);
10128
+ }
10129
+ return d.query(sql).all(...params).map(rowToEdge);
10130
+ }
10131
+ function findPath(sourceId, targetId, opts, db) {
10132
+ const d = db || getDatabase();
10133
+ const maxDepth = opts?.max_depth || 5;
10134
+ const visited = new Set;
10135
+ const queue = [{ id: sourceId, path: [] }];
10136
+ const results = [];
10137
+ visited.add(sourceId);
10138
+ while (queue.length > 0) {
10139
+ const current = queue.shift();
10140
+ if (current.path.length >= maxDepth)
10141
+ continue;
10142
+ let sql = "SELECT * FROM kg_edges WHERE source_id = ?";
10143
+ const params = [current.id];
10144
+ if (opts?.relation_types && opts.relation_types.length > 0) {
10145
+ const placeholders = opts.relation_types.map(() => "?").join(",");
10146
+ sql += ` AND relation_type IN (${placeholders})`;
10147
+ params.push(...opts.relation_types);
10148
+ }
10149
+ const edges = d.query(sql).all(...params).map(rowToEdge);
10150
+ for (const edge of edges) {
10151
+ const nextId = edge.target_id;
10152
+ const newPath = [...current.path, edge];
10153
+ if (nextId === targetId) {
10154
+ results.push(newPath);
10155
+ if (results.length >= 3)
10156
+ return results;
10157
+ continue;
10158
+ }
10159
+ if (!visited.has(nextId)) {
10160
+ visited.add(nextId);
10161
+ queue.push({ id: nextId, path: newPath });
10162
+ }
10163
+ }
10164
+ }
10165
+ return results;
10166
+ }
10167
+ function getImpactAnalysis(entityId, opts, db) {
10168
+ const d = db || getDatabase();
10169
+ const maxDepth = opts?.max_depth || 3;
10170
+ const results = [];
10171
+ const visited = new Set;
10172
+ visited.add(entityId);
10173
+ const queue = [{ id: entityId, depth: 0 }];
10174
+ while (queue.length > 0) {
10175
+ const current = queue.shift();
10176
+ if (current.depth >= maxDepth)
10177
+ continue;
10178
+ let sql = "SELECT * FROM kg_edges WHERE source_id = ?";
10179
+ const params = [current.id];
10180
+ if (opts?.relation_types && opts.relation_types.length > 0) {
10181
+ const placeholders = opts.relation_types.map(() => "?").join(",");
10182
+ sql += ` AND relation_type IN (${placeholders})`;
10183
+ params.push(...opts.relation_types);
10184
+ }
10185
+ const edges = d.query(sql).all(...params).map(rowToEdge);
10186
+ for (const edge of edges) {
10187
+ if (!visited.has(edge.target_id)) {
10188
+ visited.add(edge.target_id);
10189
+ results.push({
10190
+ entity_id: edge.target_id,
10191
+ entity_type: edge.target_type,
10192
+ depth: current.depth + 1,
10193
+ relation: edge.relation_type
10194
+ });
10195
+ queue.push({ id: edge.target_id, depth: current.depth + 1 });
10196
+ }
10197
+ }
10198
+ }
10199
+ return results;
10200
+ }
10201
+ function getCriticalPath(opts, db) {
10202
+ const d = db || getDatabase();
10203
+ let sql = `SELECT source_id, target_id FROM kg_edges WHERE relation_type = 'depends_on'`;
10204
+ const params = [];
10205
+ if (opts?.project_id) {
10206
+ sql += ` AND source_id IN (SELECT id FROM tasks WHERE project_id = ?)`;
10207
+ params.push(opts.project_id);
10208
+ }
10209
+ const edges = d.query(sql).all(...params);
10210
+ const blocks = new Map;
10211
+ for (const e of edges) {
10212
+ if (!blocks.has(e.target_id))
10213
+ blocks.set(e.target_id, new Set);
10214
+ blocks.get(e.target_id).add(e.source_id);
10215
+ }
10216
+ const results = [];
10217
+ for (const [taskId] of blocks) {
10218
+ const visited = new Set;
10219
+ const q = [taskId];
10220
+ let maxDepth = 0;
10221
+ let depth = 0;
10222
+ let levelSize = q.length;
10223
+ while (q.length > 0) {
10224
+ const node = q.shift();
10225
+ levelSize--;
10226
+ const downstream = blocks.get(node);
10227
+ if (downstream) {
10228
+ for (const d2 of downstream) {
10229
+ if (!visited.has(d2)) {
10230
+ visited.add(d2);
10231
+ q.push(d2);
10232
+ }
10233
+ }
10234
+ }
10235
+ if (levelSize === 0) {
10236
+ depth++;
10237
+ maxDepth = Math.max(maxDepth, depth);
10238
+ levelSize = q.length;
10239
+ }
10240
+ }
10241
+ if (visited.size > 0) {
10242
+ results.push({ task_id: taskId, blocking_count: visited.size, depth: maxDepth });
10243
+ }
10244
+ }
10245
+ results.sort((a, b) => b.blocking_count - a.blocking_count);
10246
+ return results.slice(0, opts?.limit || 20);
10247
+ }
10248
+ function addKgEdge(sourceId, sourceType, targetId, targetType, relationType, weight = 1, metadata, db) {
10249
+ const d = db || getDatabase();
10250
+ const id = uuid();
10251
+ const timestamp = now();
10252
+ d.run(`INSERT OR IGNORE INTO kg_edges (id, source_id, source_type, target_id, target_type, relation_type, weight, metadata, created_at)
10253
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, sourceId, sourceType, targetId, targetType, relationType, weight, JSON.stringify(metadata || {}), timestamp]);
10254
+ return { id, source_id: sourceId, source_type: sourceType, target_id: targetId, target_type: targetType, relation_type: relationType, weight, metadata: metadata || {}, created_at: timestamp };
10255
+ }
10256
+ function removeKgEdges(sourceId, targetId, relationType, db) {
10257
+ const d = db || getDatabase();
10258
+ if (relationType) {
10259
+ return d.run("DELETE FROM kg_edges WHERE source_id = ? AND target_id = ? AND relation_type = ?", [sourceId, targetId, relationType]).changes;
10260
+ }
10261
+ return d.run("DELETE FROM kg_edges WHERE source_id = ? AND target_id = ?", [sourceId, targetId]).changes;
10262
+ }
10263
+ var init_kg = __esm(() => {
10264
+ init_database();
10265
+ });
10266
+
10267
+ // src/db/patrol.ts
10268
+ var exports_patrol = {};
10269
+ __export(exports_patrol, {
10270
+ patrolTasks: () => patrolTasks,
10271
+ getReviewQueue: () => getReviewQueue
10272
+ });
10273
+ function rowToTask3(row) {
10274
+ return {
10275
+ ...row,
10276
+ status: row.status,
10277
+ priority: row.priority,
10278
+ tags: JSON.parse(row.tags || "[]"),
10279
+ metadata: JSON.parse(row.metadata || "{}"),
10280
+ requires_approval: Boolean(row.requires_approval)
10281
+ };
10282
+ }
10283
+ function patrolTasks(opts, db) {
10284
+ const d = db || getDatabase();
10285
+ const stuckMinutes = opts?.stuck_minutes || 60;
10286
+ const confidenceThreshold = opts?.confidence_threshold || 0.5;
10287
+ const issues = [];
10288
+ let projectFilter = "";
10289
+ const projectParams = [];
10290
+ if (opts?.project_id) {
10291
+ projectFilter = " AND project_id = ?";
10292
+ projectParams.push(opts.project_id);
10293
+ }
10294
+ const stuckCutoff = new Date(Date.now() - stuckMinutes * 60 * 1000).toISOString();
10295
+ const stuckRows = d.query(`SELECT * FROM tasks WHERE status = 'in_progress' AND updated_at < ?${projectFilter} ORDER BY updated_at ASC`).all(stuckCutoff, ...projectParams);
10296
+ for (const row of stuckRows) {
10297
+ const task = rowToTask3(row);
10298
+ const minutesStuck = Math.round((Date.now() - new Date(task.updated_at).getTime()) / 60000);
10299
+ issues.push({
10300
+ type: "stuck",
10301
+ task_id: task.id,
10302
+ task_title: task.title,
10303
+ severity: minutesStuck > 480 ? "critical" : minutesStuck > 120 ? "high" : "medium",
10304
+ detail: `In progress for ${minutesStuck} minutes without update`
10305
+ });
10306
+ }
10307
+ const lowConfRows = d.query(`SELECT * FROM tasks WHERE status = 'completed' AND confidence IS NOT NULL AND confidence < ?${projectFilter} ORDER BY confidence ASC`).all(confidenceThreshold, ...projectParams);
10308
+ for (const row of lowConfRows) {
10309
+ const task = rowToTask3(row);
10310
+ issues.push({
10311
+ type: "low_confidence",
10312
+ task_id: task.id,
10313
+ task_title: task.title,
10314
+ severity: (task.confidence ?? 0) < 0.3 ? "high" : "medium",
10315
+ detail: `Completed with confidence ${task.confidence} (threshold: ${confidenceThreshold})`
10316
+ });
10317
+ }
10318
+ const orphanedRows = d.query(`SELECT * FROM tasks WHERE status = 'pending' AND project_id IS NULL AND agent_id IS NULL AND assigned_to IS NULL ORDER BY created_at ASC`).all();
10319
+ for (const row of orphanedRows) {
10320
+ const task = rowToTask3(row);
10321
+ issues.push({
10322
+ type: "orphaned",
10323
+ task_id: task.id,
10324
+ task_title: task.title,
10325
+ severity: "low",
10326
+ detail: "Pending task with no project, no agent, and no assignee"
10327
+ });
10328
+ }
10329
+ const needsReviewRows = d.query(`SELECT * FROM tasks WHERE status = 'completed' AND requires_approval = 1 AND approved_by IS NULL${projectFilter} ORDER BY completed_at DESC`).all(...projectParams);
10330
+ for (const row of needsReviewRows) {
10331
+ const task = rowToTask3(row);
10332
+ issues.push({
10333
+ type: "needs_review",
10334
+ task_id: task.id,
10335
+ task_title: task.title,
10336
+ severity: "medium",
10337
+ detail: "Completed but requires approval \u2014 not yet reviewed"
10338
+ });
10339
+ }
10340
+ const pendingWithDeps = d.query(`SELECT t.* FROM tasks t
10341
+ WHERE t.status = 'pending'${projectFilter}
10342
+ AND t.id IN (SELECT task_id FROM task_dependencies)`).all(...projectParams);
10343
+ for (const row of pendingWithDeps) {
10344
+ const deps = d.query(`SELECT d.depends_on, t.status FROM task_dependencies d
10345
+ JOIN tasks t ON t.id = d.depends_on
10346
+ WHERE d.task_id = ?`).all(row.id);
10347
+ if (deps.length > 0 && deps.every((dep) => dep.status === "failed" || dep.status === "cancelled")) {
10348
+ const task = rowToTask3(row);
10349
+ issues.push({
10350
+ type: "zombie_blocked",
10351
+ task_id: task.id,
10352
+ task_title: task.title,
10353
+ severity: "high",
10354
+ detail: `Blocked by ${deps.length} task(s) that are all failed/cancelled \u2014 will never unblock`
10355
+ });
10356
+ }
10357
+ }
10358
+ const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
10359
+ issues.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
10360
+ return {
10361
+ issues,
10362
+ total_issues: issues.length,
10363
+ scanned_at: new Date().toISOString()
10364
+ };
10365
+ }
10366
+ function getReviewQueue(opts, db) {
10367
+ const d = db || getDatabase();
10368
+ let sql = `SELECT * FROM tasks WHERE status = 'completed' AND (
10369
+ (requires_approval = 1 AND approved_by IS NULL)
10370
+ OR (confidence IS NOT NULL AND confidence < 0.5)
10371
+ )`;
10372
+ const params = [];
10373
+ if (opts?.project_id) {
10374
+ sql += " AND project_id = ?";
10375
+ params.push(opts.project_id);
10376
+ }
10377
+ sql += " ORDER BY CASE WHEN requires_approval = 1 AND approved_by IS NULL THEN 0 ELSE 1 END, confidence ASC";
10378
+ if (opts?.limit) {
10379
+ sql += " LIMIT ?";
10380
+ params.push(opts.limit);
10381
+ }
10382
+ return d.query(sql).all(...params).map(rowToTask3);
10383
+ }
10384
+ var init_patrol = __esm(() => {
10385
+ init_database();
10386
+ });
10387
+
10388
+ // src/db/agent-metrics.ts
10389
+ var exports_agent_metrics = {};
10390
+ __export(exports_agent_metrics, {
10391
+ scoreTask: () => scoreTask,
10392
+ getLeaderboard: () => getLeaderboard,
10393
+ getAgentMetrics: () => getAgentMetrics
10394
+ });
10395
+ function getAgentMetrics(agentId, opts, db) {
10396
+ const d = db || getDatabase();
10397
+ const agent = d.query("SELECT id, name FROM agents WHERE id = ? OR LOWER(name) = LOWER(?)").get(agentId, agentId);
10398
+ if (!agent)
10399
+ return null;
10400
+ let projectFilter = "";
10401
+ const params = [agent.id, agent.id];
10402
+ if (opts?.project_id) {
10403
+ projectFilter = " AND project_id = ?";
10404
+ params.push(opts.project_id);
10405
+ }
10406
+ const completed = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR assigned_to = ?) AND status = 'completed'${projectFilter}`).get(...params).count;
10407
+ const failed = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR assigned_to = ?) AND status = 'failed'${projectFilter}`).get(...params).count;
10408
+ const inProgress = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR assigned_to = ?) AND status = 'in_progress'${projectFilter}`).get(...params).count;
10409
+ const total = completed + failed;
10410
+ const completionRate = total > 0 ? completed / total : 0;
10411
+ const avgTime = d.query(`SELECT AVG(
10412
+ (julianday(completed_at) - julianday(created_at)) * 24 * 60
10413
+ ) as avg_minutes
10414
+ FROM tasks
10415
+ WHERE (agent_id = ? OR assigned_to = ?) AND status = 'completed' AND completed_at IS NOT NULL${projectFilter}`).get(...params);
10416
+ const avgConf = d.query(`SELECT AVG(confidence) as avg_confidence
10417
+ FROM tasks
10418
+ WHERE (agent_id = ? OR assigned_to = ?) AND status = 'completed' AND confidence IS NOT NULL${projectFilter}`).get(...params);
10419
+ const reviewTasks = d.query(`SELECT metadata FROM tasks
10420
+ WHERE (agent_id = ? OR assigned_to = ?) AND status = 'completed'${projectFilter}
10421
+ AND metadata LIKE '%_review_score%'`).all(...params);
10422
+ let reviewScoreAvg = null;
10423
+ if (reviewTasks.length > 0) {
10424
+ let total2 = 0;
10425
+ let count = 0;
10426
+ for (const row of reviewTasks) {
10427
+ try {
10428
+ const meta = JSON.parse(row.metadata);
10429
+ if (typeof meta._review_score === "number") {
10430
+ total2 += meta._review_score;
10431
+ count++;
10432
+ }
10433
+ } catch {}
10434
+ }
10435
+ if (count > 0)
10436
+ reviewScoreAvg = total2 / count;
10437
+ }
10438
+ const speedScore = avgTime?.avg_minutes != null ? Math.max(0, 1 - avgTime.avg_minutes / (60 * 24)) : 0.5;
10439
+ const confidenceScore = avgConf?.avg_confidence ?? 0.5;
10440
+ const volumeScore = Math.min(1, completed / 50);
10441
+ const compositeScore = completionRate * 0.3 + speedScore * 0.2 + confidenceScore * 0.3 + volumeScore * 0.2;
10442
+ return {
10443
+ agent_id: agent.id,
10444
+ agent_name: agent.name,
10445
+ tasks_completed: completed,
10446
+ tasks_failed: failed,
10447
+ tasks_in_progress: inProgress,
10448
+ completion_rate: Math.round(completionRate * 1000) / 1000,
10449
+ avg_completion_minutes: avgTime?.avg_minutes != null ? Math.round(avgTime.avg_minutes * 10) / 10 : null,
10450
+ avg_confidence: avgConf?.avg_confidence != null ? Math.round(avgConf.avg_confidence * 1000) / 1000 : null,
10451
+ review_score_avg: reviewScoreAvg != null ? Math.round(reviewScoreAvg * 1000) / 1000 : null,
10452
+ composite_score: Math.round(compositeScore * 1000) / 1000
10453
+ };
10454
+ }
10455
+ function getLeaderboard(opts, db) {
10456
+ const d = db || getDatabase();
10457
+ const agents = d.query("SELECT id FROM agents ORDER BY name").all();
10458
+ const entries = [];
10459
+ for (const agent of agents) {
10460
+ const metrics = getAgentMetrics(agent.id, { project_id: opts?.project_id }, d);
10461
+ if (metrics && (metrics.tasks_completed > 0 || metrics.tasks_failed > 0 || metrics.tasks_in_progress > 0)) {
10462
+ entries.push(metrics);
10463
+ }
10464
+ }
10465
+ entries.sort((a, b) => b.composite_score - a.composite_score);
10466
+ const limit = opts?.limit || 20;
10467
+ return entries.slice(0, limit).map((entry, idx) => ({
10468
+ ...entry,
10469
+ rank: idx + 1
10470
+ }));
10471
+ }
10472
+ function scoreTask(taskId, score, reviewerId, db) {
10473
+ const d = db || getDatabase();
10474
+ if (score < 0 || score > 1)
10475
+ throw new Error("Score must be between 0 and 1");
10476
+ const task = d.query("SELECT metadata FROM tasks WHERE id = ?").get(taskId);
10477
+ if (!task)
10478
+ throw new Error(`Task not found: ${taskId}`);
10479
+ const metadata = JSON.parse(task.metadata || "{}");
10480
+ metadata._review_score = score;
10481
+ if (reviewerId)
10482
+ metadata._reviewed_by = reviewerId;
10483
+ metadata._reviewed_at = now();
10484
+ d.run("UPDATE tasks SET metadata = ?, updated_at = ? WHERE id = ?", [JSON.stringify(metadata), now(), taskId]);
10485
+ }
10486
+ var init_agent_metrics = __esm(() => {
10487
+ init_database();
10488
+ });
10489
+
9751
10490
  // src/mcp/index.ts
9752
10491
  var exports_mcp = {};
9753
10492
  __export(exports_mcp, {
@@ -10777,17 +11516,22 @@ ${text}` }] };
10777
11516
  });
10778
11517
  }
10779
11518
  if (shouldRegisterTool("register_agent")) {
10780
- server.tool("register_agent", "Register an agent. Pass session_id (unique per coding session) to prevent name conflicts. Returns conflict error if name is taken by an active agent in a different session.", {
10781
- name: exports_external.string(),
11519
+ server.tool("register_agent", "Register an agent. Name must be from the project's configured pool. Returns a conflict error with available name suggestions if the name is taken or not allowed.", {
11520
+ name: exports_external.string().describe("Agent name \u2014 must be from the project's allowed pool (Roman names by default). Use suggest_agent_name first if unsure."),
10782
11521
  description: exports_external.string().optional(),
11522
+ capabilities: exports_external.array(exports_external.string()).optional().describe("Agent capabilities/skills for task routing (e.g. ['typescript', 'testing', 'devops'])"),
10783
11523
  session_id: exports_external.string().optional().describe("Unique ID for this coding session (e.g. process PID + timestamp, or env var). Used to detect name collisions across sessions. Store it and pass on every register_agent call."),
10784
- working_dir: exports_external.string().optional().describe("Working directory of this session \u2014 helps identify who holds the name in a conflict")
10785
- }, async ({ name, description, session_id, working_dir }) => {
11524
+ working_dir: exports_external.string().optional().describe("Working directory of this session \u2014 used to look up the project's agent pool and identify who holds the name in a conflict")
11525
+ }, async ({ name, description, capabilities, session_id, working_dir }) => {
10786
11526
  try {
10787
- const result = registerAgent({ name, description, session_id, working_dir });
11527
+ const pool = getAgentPoolForProject(working_dir);
11528
+ const result = registerAgent({ name, description, capabilities, session_id, working_dir, pool });
10788
11529
  if (isAgentConflict(result)) {
11530
+ const suggestLine = result.suggestions && result.suggestions.length > 0 ? `
11531
+ Available names: ${result.suggestions.join(", ")}` : "";
11532
+ const hint = result.pool_violation ? `POOL_VIOLATION: ${result.message}${suggestLine}` : `CONFLICT: ${result.message}${suggestLine}`;
10789
11533
  return {
10790
- content: [{ type: "text", text: `CONFLICT: ${result.message}` }],
11534
+ content: [{ type: "text", text: hint }],
10791
11535
  isError: true
10792
11536
  };
10793
11537
  }
@@ -10800,6 +11544,7 @@ ID: ${agent.id}
10800
11544
  Name: ${agent.name}${agent.description ? `
10801
11545
  Description: ${agent.description}` : ""}
10802
11546
  Session: ${agent.session_id ?? "unbound"}
11547
+ Pool: [${pool.join(", ")}]
10803
11548
  Created: ${agent.created_at}
10804
11549
  Last seen: ${agent.last_seen_at}`
10805
11550
  }]
@@ -10809,6 +11554,30 @@ Last seen: ${agent.last_seen_at}`
10809
11554
  }
10810
11555
  });
10811
11556
  }
11557
+ if (shouldRegisterTool("suggest_agent_name")) {
11558
+ server.tool("suggest_agent_name", "Get available agent names for a project. Call this before register_agent to avoid conflicts.", {
11559
+ working_dir: exports_external.string().optional().describe("Your working directory \u2014 used to look up the project's allowed name pool")
11560
+ }, async ({ working_dir }) => {
11561
+ try {
11562
+ const pool = getAgentPoolForProject(working_dir);
11563
+ const available = getAvailableNamesFromPool(pool, getDatabase());
11564
+ const cutoff = new Date(Date.now() - 30 * 60 * 1000).toISOString();
11565
+ const active = listAgents().filter((a) => a.last_seen_at > cutoff && pool.map((n) => n.toLowerCase()).includes(a.name));
11566
+ const lines = [
11567
+ `Project pool: ${pool.join(", ")}`,
11568
+ `Available now (${available.length}): ${available.length > 0 ? available.join(", ") : "none \u2014 all names in use"}`,
11569
+ active.length > 0 ? `Active agents: ${active.map((a) => `${a.name} (seen ${Math.round((Date.now() - new Date(a.last_seen_at).getTime()) / 60000)}m ago)`).join(", ")}` : "Active agents: none",
11570
+ available.length > 0 ? `
11571
+ Suggested: ${available[0]}` : `
11572
+ No names available yet. Wait for an active agent to go stale (30min timeout).`
11573
+ ];
11574
+ return { content: [{ type: "text", text: lines.join(`
11575
+ `) }] };
11576
+ } catch (e) {
11577
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
11578
+ }
11579
+ });
11580
+ }
10812
11581
  if (shouldRegisterTool("list_agents")) {
10813
11582
  server.tool("list_agents", "List all registered agents", {}, async () => {
10814
11583
  try {
@@ -12001,6 +12770,7 @@ Claimed: ${formatTask(result.claimed)}`);
12001
12770
  "update_plan",
12002
12771
  "delete_plan",
12003
12772
  "register_agent",
12773
+ "suggest_agent_name",
12004
12774
  "list_agents",
12005
12775
  "get_agent",
12006
12776
  "rename_agent",
@@ -12142,9 +12912,12 @@ Claimed: ${formatTask(result.claimed)}`);
12142
12912
  delete_plan: `Delete a plan. Tasks in the plan are orphaned, not deleted.
12143
12913
  Params: id(string, req)
12144
12914
  Example: {id: 'a1b2c3d4'}`,
12145
- register_agent: `Register an agent. ALWAYS pass session_id (unique per session) to prevent name conflicts. Returns CONFLICT error if name is active in another session \u2014 pick a different name or reclaim with matching session_id.
12146
- Params: name(string, req), description(string), session_id(string \u2014 unique per session, e.g. PID+timestamp), working_dir(string)
12147
- Example: {name: 'maximus', session_id: 'abc123-1741952000', working_dir: '/workspace/platform'}`,
12915
+ suggest_agent_name: `Get available agent names for your project before registering. Shows the pool, which names are active, and the best suggestion.
12916
+ Params: working_dir(string \u2014 your working directory, used to look up project pool)
12917
+ Example: {working_dir: '/workspace/platform'}`,
12918
+ register_agent: `Register an agent. Name must be from the project's pool (call suggest_agent_name first). Returns CONFLICT or POOL_VIOLATION with suggestions if name is taken or not allowed.
12919
+ Params: name(string, req), description(string), session_id(string \u2014 unique per session, e.g. PID+timestamp), working_dir(string, req \u2014 used to determine project pool)
12920
+ Example: {name: 'cassius', session_id: 'abc123-1741952000', working_dir: '/workspace/platform'}`,
12148
12921
  list_agents: "List all registered agents with IDs, names, and last seen timestamps. No params.",
12149
12922
  get_agent: `Get agent details by ID or name. Provide one of id or name.
12150
12923
  Params: id(string), name(string)
@@ -12402,6 +13175,328 @@ ${lines.join(`
12402
13175
  }
12403
13176
  });
12404
13177
  }
13178
+ if (shouldRegisterTool("add_task_relationship")) {
13179
+ server.tool("add_task_relationship", "Create a semantic relationship between two tasks (related_to, conflicts_with, similar_to, duplicates, supersedes, modifies_same_file).", {
13180
+ source_task_id: exports_external.string().describe("Source task ID"),
13181
+ target_task_id: exports_external.string().describe("Target task ID"),
13182
+ relationship_type: exports_external.enum(["related_to", "conflicts_with", "similar_to", "duplicates", "supersedes", "modifies_same_file"]).describe("Type of relationship"),
13183
+ created_by: exports_external.string().optional().describe("Agent ID who created this relationship")
13184
+ }, async ({ source_task_id, target_task_id, relationship_type, created_by }) => {
13185
+ try {
13186
+ const { addTaskRelationship: addTaskRelationship2 } = (init_task_relationships(), __toCommonJS(exports_task_relationships));
13187
+ const rel = addTaskRelationship2({
13188
+ source_task_id: resolveId(source_task_id),
13189
+ target_task_id: resolveId(target_task_id),
13190
+ relationship_type,
13191
+ created_by
13192
+ });
13193
+ return { content: [{ type: "text", text: `Relationship created: ${rel.source_task_id.slice(0, 8)} --[${rel.relationship_type}]--> ${rel.target_task_id.slice(0, 8)}` }] };
13194
+ } catch (e) {
13195
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
13196
+ }
13197
+ });
13198
+ }
13199
+ if (shouldRegisterTool("remove_task_relationship")) {
13200
+ server.tool("remove_task_relationship", "Remove a semantic relationship between tasks by ID or by source+target+type.", {
13201
+ id: exports_external.string().optional().describe("Relationship ID to remove"),
13202
+ source_task_id: exports_external.string().optional().describe("Source task ID (use with target_task_id + type)"),
13203
+ target_task_id: exports_external.string().optional().describe("Target task ID"),
13204
+ relationship_type: exports_external.enum(["related_to", "conflicts_with", "similar_to", "duplicates", "supersedes", "modifies_same_file"]).optional()
13205
+ }, async ({ id, source_task_id, target_task_id, relationship_type }) => {
13206
+ try {
13207
+ const { removeTaskRelationship: removeTaskRelationship2, removeTaskRelationshipByPair: removeTaskRelationshipByPair2 } = (init_task_relationships(), __toCommonJS(exports_task_relationships));
13208
+ let removed = false;
13209
+ if (id) {
13210
+ removed = removeTaskRelationship2(id);
13211
+ } else if (source_task_id && target_task_id && relationship_type) {
13212
+ removed = removeTaskRelationshipByPair2(resolveId(source_task_id), resolveId(target_task_id), relationship_type);
13213
+ } else {
13214
+ return { content: [{ type: "text", text: "Provide either 'id' or 'source_task_id + target_task_id + relationship_type'" }], isError: true };
13215
+ }
13216
+ return { content: [{ type: "text", text: removed ? "Relationship removed." : "Relationship not found." }] };
13217
+ } catch (e) {
13218
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
13219
+ }
13220
+ });
13221
+ }
13222
+ if (shouldRegisterTool("get_task_relationships")) {
13223
+ server.tool("get_task_relationships", "Get all semantic relationships for a task.", {
13224
+ task_id: exports_external.string().describe("Task ID"),
13225
+ relationship_type: exports_external.enum(["related_to", "conflicts_with", "similar_to", "duplicates", "supersedes", "modifies_same_file"]).optional()
13226
+ }, async ({ task_id, relationship_type }) => {
13227
+ try {
13228
+ const { getTaskRelationships: getTaskRelationships2 } = (init_task_relationships(), __toCommonJS(exports_task_relationships));
13229
+ const rels = getTaskRelationships2(resolveId(task_id), relationship_type);
13230
+ if (rels.length === 0)
13231
+ return { content: [{ type: "text", text: "No relationships found." }] };
13232
+ const lines = rels.map((r) => `${r.source_task_id.slice(0, 8)} --[${r.relationship_type}]--> ${r.target_task_id.slice(0, 8)}${r.metadata && Object.keys(r.metadata).length > 0 ? ` (${JSON.stringify(r.metadata)})` : ""}`);
13233
+ return { content: [{ type: "text", text: lines.join(`
13234
+ `) }] };
13235
+ } catch (e) {
13236
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
13237
+ }
13238
+ });
13239
+ }
13240
+ if (shouldRegisterTool("detect_file_relationships")) {
13241
+ server.tool("detect_file_relationships", "Auto-detect tasks that modify the same files and create modifies_same_file relationships.", {
13242
+ task_id: exports_external.string().describe("Task ID to detect file relationships for")
13243
+ }, async ({ task_id }) => {
13244
+ try {
13245
+ const { autoDetectFileRelationships: autoDetectFileRelationships2 } = (init_task_relationships(), __toCommonJS(exports_task_relationships));
13246
+ const created = autoDetectFileRelationships2(resolveId(task_id));
13247
+ return { content: [{ type: "text", text: created.length > 0 ? `Created ${created.length} file relationship(s).` : "No file overlaps detected." }] };
13248
+ } catch (e) {
13249
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
13250
+ }
13251
+ });
13252
+ }
13253
+ if (shouldRegisterTool("sync_kg")) {
13254
+ server.tool("sync_kg", "Sync all existing relationships into the knowledge graph edges table. Idempotent.", {}, async () => {
13255
+ try {
13256
+ const { syncKgEdges: syncKgEdges2 } = (init_kg(), __toCommonJS(exports_kg));
13257
+ const result = syncKgEdges2();
13258
+ return { content: [{ type: "text", text: `Knowledge graph synced: ${result.synced} edge(s) processed.` }] };
13259
+ } catch (e) {
13260
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
13261
+ }
13262
+ });
13263
+ }
13264
+ if (shouldRegisterTool("get_related_entities")) {
13265
+ server.tool("get_related_entities", "Get entities related to a given entity in the knowledge graph.", {
13266
+ entity_id: exports_external.string().describe("Entity ID (task, agent, project, file path)"),
13267
+ relation_type: exports_external.string().optional().describe("Filter by relation type (depends_on, assigned_to, reports_to, references_file, in_project, in_plan, etc.)"),
13268
+ entity_type: exports_external.string().optional().describe("Filter by entity type (task, agent, project, file, plan)"),
13269
+ direction: exports_external.enum(["outgoing", "incoming", "both"]).optional().describe("Edge direction"),
13270
+ limit: exports_external.number().optional().describe("Max results")
13271
+ }, async ({ entity_id, relation_type, entity_type, direction, limit }) => {
13272
+ try {
13273
+ const { getRelated: getRelated2 } = (init_kg(), __toCommonJS(exports_kg));
13274
+ const edges = getRelated2(entity_id, { relation_type, entity_type, direction, limit });
13275
+ if (edges.length === 0)
13276
+ return { content: [{ type: "text", text: "No related entities found." }] };
13277
+ const lines = edges.map((e) => `${e.source_id.slice(0, 12)}(${e.source_type}) --[${e.relation_type}]--> ${e.target_id.slice(0, 12)}(${e.target_type})`);
13278
+ return { content: [{ type: "text", text: lines.join(`
13279
+ `) }] };
13280
+ } catch (e) {
13281
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
13282
+ }
13283
+ });
13284
+ }
13285
+ if (shouldRegisterTool("find_path")) {
13286
+ server.tool("find_path", "Find paths between two entities in the knowledge graph.", {
13287
+ source_id: exports_external.string().describe("Starting entity ID"),
13288
+ target_id: exports_external.string().describe("Target entity ID"),
13289
+ max_depth: exports_external.number().optional().describe("Maximum path depth (default: 5)"),
13290
+ relation_types: exports_external.array(exports_external.string()).optional().describe("Filter by relation types")
13291
+ }, async ({ source_id, target_id, max_depth, relation_types }) => {
13292
+ try {
13293
+ const { findPath: findPath2 } = (init_kg(), __toCommonJS(exports_kg));
13294
+ const paths = findPath2(source_id, target_id, { max_depth, relation_types });
13295
+ if (paths.length === 0)
13296
+ return { content: [{ type: "text", text: "No path found." }] };
13297
+ const lines = paths.map((path, i) => {
13298
+ const steps = path.map((e) => `${e.source_id.slice(0, 8)} --[${e.relation_type}]--> ${e.target_id.slice(0, 8)}`);
13299
+ return `Path ${i + 1} (${path.length} hops):
13300
+ ${steps.join(`
13301
+ `)}`;
13302
+ });
13303
+ return { content: [{ type: "text", text: lines.join(`
13304
+
13305
+ `) }] };
13306
+ } catch (e) {
13307
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
13308
+ }
13309
+ });
13310
+ }
13311
+ if (shouldRegisterTool("get_impact_analysis")) {
13312
+ server.tool("get_impact_analysis", "Analyze what entities are affected if a given entity changes. Traverses the knowledge graph.", {
13313
+ entity_id: exports_external.string().describe("Entity ID to analyze impact for"),
13314
+ max_depth: exports_external.number().optional().describe("Maximum traversal depth (default: 3)"),
13315
+ relation_types: exports_external.array(exports_external.string()).optional().describe("Filter by relation types")
13316
+ }, async ({ entity_id, max_depth, relation_types }) => {
13317
+ try {
13318
+ const { getImpactAnalysis: getImpactAnalysis2 } = (init_kg(), __toCommonJS(exports_kg));
13319
+ const impact = getImpactAnalysis2(entity_id, { max_depth, relation_types });
13320
+ if (impact.length === 0)
13321
+ return { content: [{ type: "text", text: "No downstream impact detected." }] };
13322
+ const byDepth = new Map;
13323
+ for (const i of impact) {
13324
+ if (!byDepth.has(i.depth))
13325
+ byDepth.set(i.depth, []);
13326
+ byDepth.get(i.depth).push(i);
13327
+ }
13328
+ const lines = [`Impact analysis: ${impact.length} affected entities`];
13329
+ for (const [depth, entities] of [...byDepth.entries()].sort((a, b) => a[0] - b[0])) {
13330
+ lines.push(`
13331
+ Depth ${depth}:`);
13332
+ for (const e of entities) {
13333
+ lines.push(` ${e.entity_id.slice(0, 12)} (${e.entity_type}) via ${e.relation}`);
13334
+ }
13335
+ }
13336
+ return { content: [{ type: "text", text: lines.join(`
13337
+ `) }] };
13338
+ } catch (e) {
13339
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
13340
+ }
13341
+ });
13342
+ }
13343
+ if (shouldRegisterTool("get_critical_path")) {
13344
+ server.tool("get_critical_path", "Find tasks that block the most downstream work (critical path analysis).", {
13345
+ project_id: exports_external.string().optional().describe("Filter by project"),
13346
+ limit: exports_external.number().optional().describe("Max results (default: 20)")
13347
+ }, async ({ project_id, limit }) => {
13348
+ try {
13349
+ const { getCriticalPath: getCriticalPath2 } = (init_kg(), __toCommonJS(exports_kg));
13350
+ const result = getCriticalPath2({ project_id: project_id ? resolveId(project_id, "projects") : undefined, limit });
13351
+ if (result.length === 0)
13352
+ return { content: [{ type: "text", text: "No critical path data. Run sync_kg first to populate the knowledge graph." }] };
13353
+ const lines = result.map((r, i) => `${i + 1}. ${r.task_id.slice(0, 8)} blocks ${r.blocking_count} task(s), max depth ${r.depth}`);
13354
+ return { content: [{ type: "text", text: `Critical path:
13355
+ ${lines.join(`
13356
+ `)}` }] };
13357
+ } catch (e) {
13358
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
13359
+ }
13360
+ });
13361
+ }
13362
+ if (shouldRegisterTool("get_capable_agents")) {
13363
+ server.tool("get_capable_agents", "Find agents that match given capabilities, sorted by match score.", {
13364
+ capabilities: exports_external.array(exports_external.string()).describe("Required capabilities to match against"),
13365
+ min_score: exports_external.number().optional().describe("Minimum match score 0.0-1.0 (default: 0.1)"),
13366
+ limit: exports_external.number().optional().describe("Max results")
13367
+ }, async ({ capabilities, min_score, limit }) => {
13368
+ try {
13369
+ const { getCapableAgents: getCapableAgents2 } = (init_agents(), __toCommonJS(exports_agents));
13370
+ const results = getCapableAgents2(capabilities, { min_score, limit });
13371
+ if (results.length === 0)
13372
+ return { content: [{ type: "text", text: "No agents match the given capabilities." }] };
13373
+ const lines = results.map((r) => `${r.agent.name} (${r.agent.id}) score:${(r.score * 100).toFixed(0)}% caps:[${r.agent.capabilities.join(",")}]`);
13374
+ return { content: [{ type: "text", text: lines.join(`
13375
+ `) }] };
13376
+ } catch (e) {
13377
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
13378
+ }
13379
+ });
13380
+ }
13381
+ if (shouldRegisterTool("patrol_tasks")) {
13382
+ server.tool("patrol_tasks", "Scan for task issues: stuck tasks, low-confidence completions, orphaned tasks, zombie-blocked tasks, and pending reviews.", {
13383
+ stuck_minutes: exports_external.number().optional().describe("Minutes threshold for stuck detection (default: 60)"),
13384
+ confidence_threshold: exports_external.number().optional().describe("Confidence threshold for low-confidence detection (default: 0.5)"),
13385
+ project_id: exports_external.string().optional().describe("Filter by project")
13386
+ }, async ({ stuck_minutes, confidence_threshold, project_id }) => {
13387
+ try {
13388
+ const { patrolTasks: patrolTasks2 } = (init_patrol(), __toCommonJS(exports_patrol));
13389
+ const result = patrolTasks2({
13390
+ stuck_minutes,
13391
+ confidence_threshold,
13392
+ project_id: project_id ? resolveId(project_id, "projects") : undefined
13393
+ });
13394
+ if (result.total_issues === 0)
13395
+ return { content: [{ type: "text", text: "All clear \u2014 no issues detected." }] };
13396
+ const lines = [`Found ${result.total_issues} issue(s):
13397
+ `];
13398
+ for (const issue of result.issues) {
13399
+ lines.push(`[${issue.severity.toUpperCase()}] ${issue.type}: ${issue.task_title.slice(0, 60)} (${issue.task_id.slice(0, 8)})`);
13400
+ lines.push(` ${issue.detail}`);
13401
+ }
13402
+ return { content: [{ type: "text", text: lines.join(`
13403
+ `) }] };
13404
+ } catch (e) {
13405
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
13406
+ }
13407
+ });
13408
+ }
13409
+ if (shouldRegisterTool("get_review_queue")) {
13410
+ server.tool("get_review_queue", "Get tasks that need review: requires_approval but unapproved, or low confidence completions.", {
13411
+ project_id: exports_external.string().optional().describe("Filter by project"),
13412
+ limit: exports_external.number().optional().describe("Max results (default: all)")
13413
+ }, async ({ project_id, limit }) => {
13414
+ try {
13415
+ const { getReviewQueue: getReviewQueue2 } = (init_patrol(), __toCommonJS(exports_patrol));
13416
+ const tasks = getReviewQueue2({
13417
+ project_id: project_id ? resolveId(project_id, "projects") : undefined,
13418
+ limit
13419
+ });
13420
+ if (tasks.length === 0)
13421
+ return { content: [{ type: "text", text: "Review queue is empty." }] };
13422
+ const lines = tasks.map((t) => {
13423
+ const conf = t.confidence != null ? ` confidence:${t.confidence}` : "";
13424
+ const approval = t.requires_approval && !t.approved_by ? " [needs approval]" : "";
13425
+ return `${t.short_id || t.id.slice(0, 8)} ${t.title.slice(0, 60)}${conf}${approval}`;
13426
+ });
13427
+ return { content: [{ type: "text", text: `Review queue (${tasks.length}):
13428
+ ${lines.join(`
13429
+ `)}` }] };
13430
+ } catch (e) {
13431
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
13432
+ }
13433
+ });
13434
+ }
13435
+ if (shouldRegisterTool("score_task")) {
13436
+ server.tool("score_task", "Score a completed task's quality (0.0-1.0). Stores in task metadata for agent performance tracking.", {
13437
+ task_id: exports_external.string().describe("Task ID to score"),
13438
+ score: exports_external.number().min(0).max(1).describe("Quality score 0.0-1.0"),
13439
+ reviewer_id: exports_external.string().optional().describe("Agent ID of reviewer")
13440
+ }, async ({ task_id, score, reviewer_id }) => {
13441
+ try {
13442
+ const { scoreTask: scoreTask2 } = (init_agent_metrics(), __toCommonJS(exports_agent_metrics));
13443
+ scoreTask2(resolveId(task_id), score, reviewer_id);
13444
+ return { content: [{ type: "text", text: `Task ${task_id.slice(0, 8)} scored: ${score}` }] };
13445
+ } catch (e) {
13446
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
13447
+ }
13448
+ });
13449
+ }
13450
+ if (shouldRegisterTool("get_agent_metrics")) {
13451
+ server.tool("get_agent_metrics", "Get performance metrics for an agent: completion rate, speed, confidence, review scores.", {
13452
+ agent_id: exports_external.string().describe("Agent ID or name"),
13453
+ project_id: exports_external.string().optional().describe("Filter by project")
13454
+ }, async ({ agent_id, project_id }) => {
13455
+ try {
13456
+ const { getAgentMetrics: getAgentMetrics2 } = (init_agent_metrics(), __toCommonJS(exports_agent_metrics));
13457
+ const metrics = getAgentMetrics2(agent_id, {
13458
+ project_id: project_id ? resolveId(project_id, "projects") : undefined
13459
+ });
13460
+ if (!metrics)
13461
+ return { content: [{ type: "text", text: `Agent not found: ${agent_id}` }], isError: true };
13462
+ const lines = [
13463
+ `Agent: ${metrics.agent_name} (${metrics.agent_id})`,
13464
+ `Completed: ${metrics.tasks_completed} | Failed: ${metrics.tasks_failed} | In Progress: ${metrics.tasks_in_progress}`,
13465
+ `Completion Rate: ${(metrics.completion_rate * 100).toFixed(1)}%`,
13466
+ metrics.avg_completion_minutes != null ? `Avg Completion Time: ${metrics.avg_completion_minutes} min` : null,
13467
+ metrics.avg_confidence != null ? `Avg Confidence: ${(metrics.avg_confidence * 100).toFixed(1)}%` : null,
13468
+ metrics.review_score_avg != null ? `Avg Review Score: ${(metrics.review_score_avg * 100).toFixed(1)}%` : null,
13469
+ `Composite Score: ${(metrics.composite_score * 100).toFixed(1)}%`
13470
+ ].filter(Boolean);
13471
+ return { content: [{ type: "text", text: lines.join(`
13472
+ `) }] };
13473
+ } catch (e) {
13474
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
13475
+ }
13476
+ });
13477
+ }
13478
+ if (shouldRegisterTool("get_leaderboard")) {
13479
+ server.tool("get_leaderboard", "Get agent leaderboard ranked by composite performance score.", {
13480
+ project_id: exports_external.string().optional().describe("Filter by project"),
13481
+ limit: exports_external.number().optional().describe("Max entries (default: 20)")
13482
+ }, async ({ project_id, limit }) => {
13483
+ try {
13484
+ const { getLeaderboard: getLeaderboard2 } = (init_agent_metrics(), __toCommonJS(exports_agent_metrics));
13485
+ const entries = getLeaderboard2({
13486
+ project_id: project_id ? resolveId(project_id, "projects") : undefined,
13487
+ limit
13488
+ });
13489
+ if (entries.length === 0)
13490
+ return { content: [{ type: "text", text: "No agents with task activity found." }] };
13491
+ const lines = entries.map((e) => `#${e.rank} ${e.agent_name.padEnd(15)} score:${(e.composite_score * 100).toFixed(0).padStart(3)}% done:${String(e.tasks_completed).padStart(3)} rate:${(e.completion_rate * 100).toFixed(0)}%`);
13492
+ return { content: [{ type: "text", text: `Leaderboard:
13493
+ ${lines.join(`
13494
+ `)}` }] };
13495
+ } catch (e) {
13496
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
13497
+ }
13498
+ });
13499
+ }
12405
13500
  server.resource("task-lists", "todos://task-lists", { description: "All task lists", mimeType: "application/json" }, async () => {
12406
13501
  const lists = listTaskLists();
12407
13502
  return { contents: [{ uri: "todos://task-lists", text: JSON.stringify(lists, null, 2), mimeType: "application/json" }] };