@hasna/todos 0.11.14 → 0.11.16

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.
Files changed (157) hide show
  1. package/dashboard/dist/assets/index-B-w1tUlm.js +346 -0
  2. package/dashboard/dist/assets/index-BXQ39iMX.css +1 -0
  3. package/dashboard/dist/index.html +13 -0
  4. package/dashboard/dist/logo.jpg +0 -0
  5. package/dist/cli/brains.d.ts +3 -0
  6. package/dist/cli/brains.d.ts.map +1 -0
  7. package/dist/cli/commands/dispatch.d.ts +3 -0
  8. package/dist/cli/commands/dispatch.d.ts.map +1 -0
  9. package/dist/cli/components/App.d.ts +2 -0
  10. package/dist/cli/components/App.d.ts.map +1 -0
  11. package/dist/cli/components/Dashboard.d.ts +7 -0
  12. package/dist/cli/components/Dashboard.d.ts.map +1 -0
  13. package/dist/cli/components/Header.d.ts +8 -0
  14. package/dist/cli/components/Header.d.ts.map +1 -0
  15. package/dist/cli/components/ProjectList.d.ts +8 -0
  16. package/dist/cli/components/ProjectList.d.ts.map +1 -0
  17. package/dist/cli/components/SearchView.d.ts +10 -0
  18. package/dist/cli/components/SearchView.d.ts.map +1 -0
  19. package/dist/cli/components/TaskDetail.d.ts +7 -0
  20. package/dist/cli/components/TaskDetail.d.ts.map +1 -0
  21. package/dist/cli/components/TaskForm.d.ts +15 -0
  22. package/dist/cli/components/TaskForm.d.ts.map +1 -0
  23. package/dist/cli/components/TaskList.d.ts +8 -0
  24. package/dist/cli/components/TaskList.d.ts.map +1 -0
  25. package/dist/cli/index.d.ts +3 -0
  26. package/dist/cli/index.d.ts.map +1 -0
  27. package/dist/cli/index.js +4975 -4121
  28. package/dist/db/agent-metrics.d.ts +34 -0
  29. package/dist/db/agent-metrics.d.ts.map +1 -0
  30. package/dist/db/agents.d.ts +82 -0
  31. package/dist/db/agents.d.ts.map +1 -0
  32. package/dist/db/audit.d.ts +52 -0
  33. package/dist/db/audit.d.ts.map +1 -0
  34. package/dist/db/budgets.d.ts +27 -0
  35. package/dist/db/budgets.d.ts.map +1 -0
  36. package/dist/db/builtin-templates.d.ts +22 -0
  37. package/dist/db/builtin-templates.d.ts.map +1 -0
  38. package/dist/db/checklists.d.ts +13 -0
  39. package/dist/db/checklists.d.ts.map +1 -0
  40. package/dist/db/comments.d.ts +8 -0
  41. package/dist/db/comments.d.ts.map +1 -0
  42. package/dist/db/database.d.ts +12 -0
  43. package/dist/db/database.d.ts.map +1 -0
  44. package/dist/db/dispatches.d.ts +15 -0
  45. package/dist/db/dispatches.d.ts.map +1 -0
  46. package/dist/db/file-locks.d.ts +43 -0
  47. package/dist/db/file-locks.d.ts.map +1 -0
  48. package/dist/db/handoffs.d.ts +25 -0
  49. package/dist/db/handoffs.d.ts.map +1 -0
  50. package/dist/db/kg.d.ts +70 -0
  51. package/dist/db/kg.d.ts.map +1 -0
  52. package/dist/db/locks.d.ts +14 -0
  53. package/dist/db/locks.d.ts.map +1 -0
  54. package/dist/db/machines.d.ts +17 -0
  55. package/dist/db/machines.d.ts.map +1 -0
  56. package/dist/db/orgs.d.ts +13 -0
  57. package/dist/db/orgs.d.ts.map +1 -0
  58. package/dist/db/patrol.d.ts +35 -0
  59. package/dist/db/patrol.d.ts.map +1 -0
  60. package/dist/db/pg-migrate.d.ts +14 -0
  61. package/dist/db/pg-migrate.d.ts.map +1 -0
  62. package/dist/db/pg-migrations.d.ts +8 -0
  63. package/dist/db/pg-migrations.d.ts.map +1 -0
  64. package/dist/db/plans.d.ts +8 -0
  65. package/dist/db/plans.d.ts.map +1 -0
  66. package/dist/db/project-agent-roles.d.ts +34 -0
  67. package/dist/db/project-agent-roles.d.ts.map +1 -0
  68. package/dist/db/projects.d.ts +16 -0
  69. package/dist/db/projects.d.ts.map +1 -0
  70. package/dist/db/schema.d.ts +6 -0
  71. package/dist/db/schema.d.ts.map +1 -0
  72. package/dist/db/sessions.d.ts +8 -0
  73. package/dist/db/sessions.d.ts.map +1 -0
  74. package/dist/db/snapshots.d.ts +37 -0
  75. package/dist/db/snapshots.d.ts.map +1 -0
  76. package/dist/db/task-claim.d.ts +7 -0
  77. package/dist/db/task-claim.d.ts.map +1 -0
  78. package/dist/db/task-commits.d.ts +31 -0
  79. package/dist/db/task-commits.d.ts.map +1 -0
  80. package/dist/db/task-files.d.ts +74 -0
  81. package/dist/db/task-files.d.ts.map +1 -0
  82. package/dist/db/task-lists.d.ts +10 -0
  83. package/dist/db/task-lists.d.ts.map +1 -0
  84. package/dist/db/task-relationships.d.ts +36 -0
  85. package/dist/db/task-relationships.d.ts.map +1 -0
  86. package/dist/db/task-workflow.d.ts +7 -0
  87. package/dist/db/task-workflow.d.ts.map +1 -0
  88. package/dist/db/tasks.d.ts +215 -0
  89. package/dist/db/tasks.d.ts.map +1 -0
  90. package/dist/db/templates.d.ts +98 -0
  91. package/dist/db/templates.d.ts.map +1 -0
  92. package/dist/db/traces.d.ts +38 -0
  93. package/dist/db/traces.d.ts.map +1 -0
  94. package/dist/db/webhooks.d.ts +19 -0
  95. package/dist/db/webhooks.d.ts.map +1 -0
  96. package/dist/index.d.ts +72 -0
  97. package/dist/index.d.ts.map +1 -0
  98. package/dist/index.js +344 -140
  99. package/dist/lib/agent-tasks.d.ts +11 -0
  100. package/dist/lib/agent-tasks.d.ts.map +1 -0
  101. package/dist/lib/auto-assign.d.ts +25 -0
  102. package/dist/lib/auto-assign.d.ts.map +1 -0
  103. package/dist/lib/burndown.d.ts +18 -0
  104. package/dist/lib/burndown.d.ts.map +1 -0
  105. package/dist/lib/claude-tasks.d.ts +20 -0
  106. package/dist/lib/claude-tasks.d.ts.map +1 -0
  107. package/dist/lib/completion-guard.d.ts +17 -0
  108. package/dist/lib/completion-guard.d.ts.map +1 -0
  109. package/dist/lib/config.d.ts +44 -0
  110. package/dist/lib/config.d.ts.map +1 -0
  111. package/dist/lib/dispatch-formatter.d.ts +21 -0
  112. package/dist/lib/dispatch-formatter.d.ts.map +1 -0
  113. package/dist/lib/dispatch.d.ts +28 -0
  114. package/dist/lib/dispatch.d.ts.map +1 -0
  115. package/dist/lib/extract.d.ts +51 -0
  116. package/dist/lib/extract.d.ts.map +1 -0
  117. package/dist/lib/gatherer.d.ts +16 -0
  118. package/dist/lib/gatherer.d.ts.map +1 -0
  119. package/dist/lib/github.d.ts +25 -0
  120. package/dist/lib/github.d.ts.map +1 -0
  121. package/dist/lib/model-config.d.ts +14 -0
  122. package/dist/lib/model-config.d.ts.map +1 -0
  123. package/dist/lib/recurrence.d.ts +10 -0
  124. package/dist/lib/recurrence.d.ts.map +1 -0
  125. package/dist/lib/search.d.ts +17 -0
  126. package/dist/lib/search.d.ts.map +1 -0
  127. package/dist/lib/sync-types.d.ts +16 -0
  128. package/dist/lib/sync-types.d.ts.map +1 -0
  129. package/dist/lib/sync-utils.d.ts +12 -0
  130. package/dist/lib/sync-utils.d.ts.map +1 -0
  131. package/dist/lib/sync.d.ts +9 -0
  132. package/dist/lib/sync.d.ts.map +1 -0
  133. package/dist/lib/tmux.d.ts +28 -0
  134. package/dist/lib/tmux.d.ts.map +1 -0
  135. package/dist/mcp/index.d.ts +3 -0
  136. package/dist/mcp/index.d.ts.map +1 -0
  137. package/dist/mcp/index.js +14175 -13694
  138. package/dist/mcp/tools/agents.d.ts +16 -0
  139. package/dist/mcp/tools/agents.d.ts.map +1 -0
  140. package/dist/mcp/tools/cloud.d.ts +12 -0
  141. package/dist/mcp/tools/cloud.d.ts.map +1 -0
  142. package/dist/mcp/tools/dispatch.d.ts +9 -0
  143. package/dist/mcp/tools/dispatch.d.ts.map +1 -0
  144. package/dist/mcp/tools/templates.d.ts +9 -0
  145. package/dist/mcp/tools/templates.d.ts.map +1 -0
  146. package/dist/mcp/tools/webhooks.d.ts +8 -0
  147. package/dist/mcp/tools/webhooks.d.ts.map +1 -0
  148. package/dist/sdk.d.ts +186 -0
  149. package/dist/sdk.d.ts.map +1 -0
  150. package/dist/server/index.d.ts +9 -0
  151. package/dist/server/index.d.ts.map +1 -0
  152. package/dist/server/index.js +4933 -0
  153. package/dist/server/serve.d.ts +10 -0
  154. package/dist/server/serve.d.ts.map +1 -0
  155. package/dist/types/index.d.ts +681 -0
  156. package/dist/types/index.d.ts.map +1 -0
  157. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -15,84 +15,7 @@ var __export = (target, all) => {
15
15
  };
16
16
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
17
 
18
- // src/db/database.ts
19
- import { Database } from "bun:sqlite";
20
- import { existsSync, mkdirSync } from "fs";
21
- import { dirname, join, resolve } from "path";
22
- function isInMemoryDb(path) {
23
- return path === ":memory:" || path.startsWith("file::memory:");
24
- }
25
- function findNearestTodosDb(startDir) {
26
- let dir = resolve(startDir);
27
- while (true) {
28
- const candidate = join(dir, ".todos", "todos.db");
29
- if (existsSync(candidate))
30
- return candidate;
31
- const parent = dirname(dir);
32
- if (parent === dir)
33
- break;
34
- dir = parent;
35
- }
36
- return null;
37
- }
38
- function findGitRoot(startDir) {
39
- let dir = resolve(startDir);
40
- while (true) {
41
- if (existsSync(join(dir, ".git")))
42
- return dir;
43
- const parent = dirname(dir);
44
- if (parent === dir)
45
- break;
46
- dir = parent;
47
- }
48
- return null;
49
- }
50
- function getDbPath() {
51
- if (process.env["HASNA_TODOS_DB_PATH"]) {
52
- return process.env["HASNA_TODOS_DB_PATH"];
53
- }
54
- if (process.env["TODOS_DB_PATH"]) {
55
- return process.env["TODOS_DB_PATH"];
56
- }
57
- const cwd = process.cwd();
58
- const nearest = findNearestTodosDb(cwd);
59
- if (nearest)
60
- return nearest;
61
- if (process.env["TODOS_DB_SCOPE"] === "project") {
62
- const gitRoot = findGitRoot(cwd);
63
- if (gitRoot) {
64
- return join(gitRoot, ".todos", "todos.db");
65
- }
66
- }
67
- const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
68
- const newPath = join(home, ".hasna", "todos", "todos.db");
69
- const legacyPath = join(home, ".todos", "todos.db");
70
- if (!existsSync(newPath) && existsSync(legacyPath)) {
71
- return legacyPath;
72
- }
73
- return newPath;
74
- }
75
- function ensureDir(filePath) {
76
- if (isInMemoryDb(filePath))
77
- return;
78
- const dir = dirname(resolve(filePath));
79
- if (!existsSync(dir)) {
80
- mkdirSync(dir, { recursive: true });
81
- }
82
- }
83
- function getDatabase(dbPath) {
84
- if (_db)
85
- return _db;
86
- const path = dbPath || getDbPath();
87
- ensureDir(path);
88
- _db = new Database(path);
89
- _db.run("PRAGMA journal_mode = WAL");
90
- _db.run("PRAGMA busy_timeout = 5000");
91
- _db.run("PRAGMA foreign_keys = ON");
92
- runMigrations(_db);
93
- backfillTaskTags(_db);
94
- return _db;
95
- }
18
+ // src/db/schema.ts
96
19
  function runMigrations(db) {
97
20
  try {
98
21
  const result = db.query("SELECT MAX(id) as max_id FROM _migrations").get();
@@ -253,6 +176,13 @@ function ensureSchema(db) {
253
176
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
254
177
  UNIQUE(source_id, source_type, target_id, target_type, relation_type)
255
178
  )`);
179
+ ensureTable("machines", `
180
+ CREATE TABLE machines (
181
+ id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, hostname TEXT, platform TEXT,
182
+ last_seen_at TEXT NOT NULL DEFAULT (datetime('now')),
183
+ metadata TEXT DEFAULT '{}',
184
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
185
+ )`);
256
186
  ensureColumn("projects", "task_list_id", "TEXT");
257
187
  ensureColumn("projects", "task_prefix", "TEXT");
258
188
  ensureColumn("projects", "task_counter", "INTEGER NOT NULL DEFAULT 0");
@@ -353,6 +283,38 @@ function ensureSchema(db) {
353
283
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_webhook_deliveries_event ON webhook_deliveries(event)");
354
284
  ensureColumn("task_comments", "type", "TEXT DEFAULT 'comment'");
355
285
  ensureColumn("task_comments", "progress_pct", "INTEGER");
286
+ ensureColumn("projects", "machine_id", "TEXT");
287
+ ensureColumn("projects", "synced_at", "TEXT");
288
+ ensureColumn("tasks", "machine_id", "TEXT");
289
+ ensureColumn("tasks", "synced_at", "TEXT");
290
+ ensureColumn("agents", "machine_id", "TEXT");
291
+ ensureColumn("agents", "synced_at", "TEXT");
292
+ ensureColumn("task_lists", "machine_id", "TEXT");
293
+ ensureColumn("task_lists", "synced_at", "TEXT");
294
+ ensureColumn("plans", "machine_id", "TEXT");
295
+ ensureColumn("plans", "synced_at", "TEXT");
296
+ ensureColumn("task_comments", "machine_id", "TEXT");
297
+ ensureColumn("task_comments", "synced_at", "TEXT");
298
+ ensureColumn("sessions", "machine_id", "TEXT");
299
+ ensureColumn("sessions", "synced_at", "TEXT");
300
+ ensureColumn("task_history", "machine_id", "TEXT");
301
+ ensureColumn("webhooks", "machine_id", "TEXT");
302
+ ensureColumn("webhooks", "synced_at", "TEXT");
303
+ ensureColumn("task_templates", "machine_id", "TEXT");
304
+ ensureColumn("task_templates", "synced_at", "TEXT");
305
+ ensureColumn("orgs", "machine_id", "TEXT");
306
+ ensureColumn("orgs", "synced_at", "TEXT");
307
+ ensureColumn("handoffs", "machine_id", "TEXT");
308
+ ensureColumn("handoffs", "synced_at", "TEXT");
309
+ ensureColumn("task_checklists", "machine_id", "TEXT");
310
+ ensureColumn("project_sources", "machine_id", "TEXT");
311
+ ensureColumn("project_sources", "synced_at", "TEXT");
312
+ ensureColumn("task_files", "machine_id", "TEXT");
313
+ ensureColumn("task_relationships", "machine_id", "TEXT");
314
+ ensureColumn("kg_edges", "machine_id", "TEXT");
315
+ ensureColumn("project_agent_roles", "machine_id", "TEXT");
316
+ ensureColumn("dispatches", "machine_id", "TEXT");
317
+ ensureColumn("dispatches", "synced_at", "TEXT");
356
318
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_plan ON tasks(plan_id)");
357
319
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_task_list ON tasks(task_list_id)");
358
320
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_due_at ON tasks(due_at)");
@@ -381,6 +343,10 @@ function ensureSchema(db) {
381
343
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_kg_source ON kg_edges(source_id, source_type)");
382
344
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_kg_target ON kg_edges(target_id, target_type)");
383
345
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_kg_relation ON kg_edges(relation_type)");
346
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_machine ON tasks(machine_id)");
347
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_synced ON tasks(synced_at)");
348
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_projects_machine ON projects(machine_id)");
349
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_agents_machine ON agents(machine_id)");
384
350
  }
385
351
  function backfillTaskTags(db) {
386
352
  try {
@@ -411,68 +377,8 @@ function backfillTaskTags(db) {
411
377
  }
412
378
  } catch {}
413
379
  }
414
- function closeDatabase() {
415
- if (_db) {
416
- _db.close();
417
- _db = null;
418
- }
419
- }
420
- function resetDatabase() {
421
- _db = null;
422
- }
423
- function now() {
424
- return new Date().toISOString();
425
- }
426
- function uuid() {
427
- return crypto.randomUUID();
428
- }
429
- function isLockExpired(lockedAt) {
430
- if (!lockedAt)
431
- return true;
432
- const lockTime = new Date(lockedAt).getTime();
433
- const expiryMs = LOCK_EXPIRY_MINUTES * 60 * 1000;
434
- return Date.now() - lockTime > expiryMs;
435
- }
436
- function lockExpiryCutoff(nowMs = Date.now()) {
437
- const expiryMs = LOCK_EXPIRY_MINUTES * 60 * 1000;
438
- return new Date(nowMs - expiryMs).toISOString();
439
- }
440
- function clearExpiredLocks(db) {
441
- const cutoff = lockExpiryCutoff();
442
- db.run("UPDATE tasks SET locked_by = NULL, locked_at = NULL WHERE locked_at IS NOT NULL AND locked_at < ?", [cutoff]);
443
- }
444
- function resolvePartialId(db, table, partialId) {
445
- if (partialId.length >= 36) {
446
- const row = db.query(`SELECT id FROM ${table} WHERE id = ?`).get(partialId);
447
- return row?.id ?? null;
448
- }
449
- const rows = db.query(`SELECT id FROM ${table} WHERE id LIKE ?`).all(`${partialId}%`);
450
- if (rows.length === 1) {
451
- return rows[0].id;
452
- }
453
- if (rows.length > 1) {
454
- return null;
455
- }
456
- if (table === "tasks") {
457
- const shortIdRows = db.query("SELECT id FROM tasks WHERE short_id = ?").all(partialId);
458
- if (shortIdRows.length === 1) {
459
- return shortIdRows[0].id;
460
- }
461
- }
462
- if (table === "task_lists") {
463
- const slugRow = db.query("SELECT id FROM task_lists WHERE slug = ?").get(partialId);
464
- if (slugRow)
465
- return slugRow.id;
466
- }
467
- if (table === "projects") {
468
- const nameRow = db.query("SELECT id FROM projects WHERE lower(name) = ?").get(partialId.toLowerCase());
469
- if (nameRow)
470
- return nameRow.id;
471
- }
472
- return null;
473
- }
474
- var LOCK_EXPIRY_MINUTES = 30, MIGRATIONS, _db = null;
475
- var init_database = __esm(() => {
380
+ var MIGRATIONS;
381
+ var init_schema = __esm(() => {
476
382
  MIGRATIONS = [
477
383
  `
478
384
  CREATE TABLE IF NOT EXISTS projects (
@@ -1092,10 +998,297 @@ var init_database = __esm(() => {
1092
998
  CREATE INDEX IF NOT EXISTS idx_dispatch_logs_dispatch ON dispatch_logs(dispatch_id);
1093
999
 
1094
1000
  INSERT OR IGNORE INTO _migrations (id) VALUES (40);
1001
+ `,
1002
+ `
1003
+ CREATE TABLE IF NOT EXISTS machines (
1004
+ id TEXT PRIMARY KEY,
1005
+ name TEXT NOT NULL UNIQUE,
1006
+ hostname TEXT,
1007
+ platform TEXT,
1008
+ last_seen_at TEXT NOT NULL DEFAULT (datetime('now')),
1009
+ metadata TEXT DEFAULT '{}',
1010
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
1011
+ );
1012
+
1013
+ ALTER TABLE projects ADD COLUMN machine_id TEXT;
1014
+ ALTER TABLE projects ADD COLUMN synced_at TEXT;
1015
+ ALTER TABLE tasks ADD COLUMN machine_id TEXT;
1016
+ ALTER TABLE tasks ADD COLUMN synced_at TEXT;
1017
+ ALTER TABLE agents ADD COLUMN machine_id TEXT;
1018
+ ALTER TABLE agents ADD COLUMN synced_at TEXT;
1019
+ ALTER TABLE task_lists ADD COLUMN machine_id TEXT;
1020
+ ALTER TABLE task_lists ADD COLUMN synced_at TEXT;
1021
+ ALTER TABLE plans ADD COLUMN machine_id TEXT;
1022
+ ALTER TABLE plans ADD COLUMN synced_at TEXT;
1023
+ ALTER TABLE task_comments ADD COLUMN machine_id TEXT;
1024
+ ALTER TABLE task_comments ADD COLUMN synced_at TEXT;
1025
+ ALTER TABLE sessions ADD COLUMN machine_id TEXT;
1026
+ ALTER TABLE sessions ADD COLUMN synced_at TEXT;
1027
+ ALTER TABLE task_history ADD COLUMN machine_id TEXT;
1028
+ ALTER TABLE webhooks ADD COLUMN machine_id TEXT;
1029
+ ALTER TABLE webhooks ADD COLUMN synced_at TEXT;
1030
+ ALTER TABLE task_templates ADD COLUMN machine_id TEXT;
1031
+ ALTER TABLE task_templates ADD COLUMN synced_at TEXT;
1032
+ ALTER TABLE orgs ADD COLUMN machine_id TEXT;
1033
+ ALTER TABLE orgs ADD COLUMN synced_at TEXT;
1034
+ ALTER TABLE handoffs ADD COLUMN machine_id TEXT;
1035
+ ALTER TABLE handoffs ADD COLUMN synced_at TEXT;
1036
+ ALTER TABLE task_checklists ADD COLUMN machine_id TEXT;
1037
+ ALTER TABLE project_sources ADD COLUMN machine_id TEXT;
1038
+ ALTER TABLE project_sources ADD COLUMN synced_at TEXT;
1039
+ ALTER TABLE task_files ADD COLUMN machine_id TEXT;
1040
+ ALTER TABLE task_relationships ADD COLUMN machine_id TEXT;
1041
+ ALTER TABLE kg_edges ADD COLUMN machine_id TEXT;
1042
+ ALTER TABLE project_agent_roles ADD COLUMN machine_id TEXT;
1043
+ ALTER TABLE dispatches ADD COLUMN machine_id TEXT;
1044
+ ALTER TABLE dispatches ADD COLUMN synced_at TEXT;
1045
+
1046
+ CREATE INDEX IF NOT EXISTS idx_tasks_machine ON tasks(machine_id);
1047
+ CREATE INDEX IF NOT EXISTS idx_tasks_synced ON tasks(synced_at);
1048
+ CREATE INDEX IF NOT EXISTS idx_projects_machine ON projects(machine_id);
1049
+ CREATE INDEX IF NOT EXISTS idx_agents_machine ON agents(machine_id);
1050
+
1051
+ INSERT OR IGNORE INTO _migrations (id) VALUES (41);
1095
1052
  `
1096
1053
  ];
1097
1054
  });
1098
1055
 
1056
+ // src/db/machines.ts
1057
+ import { hostname as osHostname, platform as osPlatform } from "os";
1058
+ function rowToMachine(row) {
1059
+ return {
1060
+ ...row,
1061
+ metadata: row.metadata ? JSON.parse(row.metadata) : {}
1062
+ };
1063
+ }
1064
+ function getOrCreateLocalMachine(db) {
1065
+ const d = db || getDatabase();
1066
+ const name = process.env["TODOS_MACHINE_NAME"] || osHostname();
1067
+ const host = osHostname();
1068
+ const plat = osPlatform();
1069
+ const existing = d.query("SELECT * FROM machines WHERE name = ?").get(name);
1070
+ if (existing) {
1071
+ d.run("UPDATE machines SET hostname = ?, platform = ?, last_seen_at = ? WHERE id = ?", [host, plat, now(), existing.id]);
1072
+ return rowToMachine({ ...existing, hostname: host, platform: plat, last_seen_at: now() });
1073
+ }
1074
+ const id = uuid();
1075
+ const ts = now();
1076
+ d.run("INSERT INTO machines (id, name, hostname, platform, last_seen_at, metadata, created_at) VALUES (?, ?, ?, ?, ?, '{}', ?)", [id, name, host, plat, ts, ts]);
1077
+ return { id, name, hostname: host, platform: plat, last_seen_at: ts, metadata: {}, created_at: ts };
1078
+ }
1079
+ function getMachineId(db) {
1080
+ if (_machineId)
1081
+ return _machineId;
1082
+ const machine = getOrCreateLocalMachine(db);
1083
+ _machineId = machine.id;
1084
+ return _machineId;
1085
+ }
1086
+ function resetMachineId() {
1087
+ _machineId = null;
1088
+ }
1089
+ function getMachine(id, db) {
1090
+ const d = db || getDatabase();
1091
+ const row = d.query("SELECT * FROM machines WHERE id = ?").get(id);
1092
+ return row ? rowToMachine(row) : null;
1093
+ }
1094
+ function getMachineByName(name, db) {
1095
+ const d = db || getDatabase();
1096
+ const row = d.query("SELECT * FROM machines WHERE name = ?").get(name);
1097
+ return row ? rowToMachine(row) : null;
1098
+ }
1099
+ function listMachines(db) {
1100
+ const d = db || getDatabase();
1101
+ const rows = d.query("SELECT * FROM machines ORDER BY last_seen_at DESC").all();
1102
+ return rows.map(rowToMachine);
1103
+ }
1104
+ function deleteMachine(id, db) {
1105
+ const d = db || getDatabase();
1106
+ const result = d.run("DELETE FROM machines WHERE id = ?", [id]);
1107
+ return result.changes > 0;
1108
+ }
1109
+ function backfillMachineId(db, force = false) {
1110
+ if (!force && process.env["TODOS_DB_PATH"] === ":memory:")
1111
+ return;
1112
+ try {
1113
+ const machine = getOrCreateLocalMachine(db);
1114
+ for (const table of TABLES_WITH_MACHINE_ID) {
1115
+ try {
1116
+ db.run(`UPDATE "${table}" SET machine_id = ? WHERE machine_id IS NULL`, [machine.id]);
1117
+ } catch {}
1118
+ }
1119
+ } catch {}
1120
+ }
1121
+ var _machineId = null, TABLES_WITH_MACHINE_ID;
1122
+ var init_machines = __esm(() => {
1123
+ init_database();
1124
+ TABLES_WITH_MACHINE_ID = [
1125
+ "projects",
1126
+ "tasks",
1127
+ "agents",
1128
+ "task_lists",
1129
+ "plans",
1130
+ "task_comments",
1131
+ "sessions",
1132
+ "task_history",
1133
+ "webhooks",
1134
+ "task_templates",
1135
+ "orgs",
1136
+ "handoffs",
1137
+ "task_checklists",
1138
+ "project_sources",
1139
+ "task_files",
1140
+ "task_relationships",
1141
+ "kg_edges",
1142
+ "project_agent_roles",
1143
+ "dispatches"
1144
+ ];
1145
+ });
1146
+
1147
+ // src/db/database.ts
1148
+ import { Database } from "bun:sqlite";
1149
+ import { existsSync, mkdirSync } from "fs";
1150
+ import { dirname, join, resolve } from "path";
1151
+ function isInMemoryDb(path) {
1152
+ return path === ":memory:" || path.startsWith("file::memory:");
1153
+ }
1154
+ function findNearestTodosDb(startDir) {
1155
+ let dir = resolve(startDir);
1156
+ while (true) {
1157
+ const candidate = join(dir, ".todos", "todos.db");
1158
+ if (existsSync(candidate))
1159
+ return candidate;
1160
+ const parent = dirname(dir);
1161
+ if (parent === dir)
1162
+ break;
1163
+ dir = parent;
1164
+ }
1165
+ return null;
1166
+ }
1167
+ function findGitRoot(startDir) {
1168
+ let dir = resolve(startDir);
1169
+ while (true) {
1170
+ if (existsSync(join(dir, ".git")))
1171
+ return dir;
1172
+ const parent = dirname(dir);
1173
+ if (parent === dir)
1174
+ break;
1175
+ dir = parent;
1176
+ }
1177
+ return null;
1178
+ }
1179
+ function getDbPath() {
1180
+ if (process.env["HASNA_TODOS_DB_PATH"]) {
1181
+ return process.env["HASNA_TODOS_DB_PATH"];
1182
+ }
1183
+ if (process.env["TODOS_DB_PATH"]) {
1184
+ return process.env["TODOS_DB_PATH"];
1185
+ }
1186
+ const cwd = process.cwd();
1187
+ const nearest = findNearestTodosDb(cwd);
1188
+ if (nearest)
1189
+ return nearest;
1190
+ if (process.env["TODOS_DB_SCOPE"] === "project") {
1191
+ const gitRoot = findGitRoot(cwd);
1192
+ if (gitRoot) {
1193
+ return join(gitRoot, ".todos", "todos.db");
1194
+ }
1195
+ }
1196
+ const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
1197
+ const newPath = join(home, ".hasna", "todos", "todos.db");
1198
+ const legacyPath = join(home, ".todos", "todos.db");
1199
+ if (!existsSync(newPath) && existsSync(legacyPath)) {
1200
+ return legacyPath;
1201
+ }
1202
+ return newPath;
1203
+ }
1204
+ function ensureDir(filePath) {
1205
+ if (isInMemoryDb(filePath))
1206
+ return;
1207
+ const dir = dirname(resolve(filePath));
1208
+ if (!existsSync(dir)) {
1209
+ mkdirSync(dir, { recursive: true });
1210
+ }
1211
+ }
1212
+ function getDatabase(dbPath) {
1213
+ if (_db)
1214
+ return _db;
1215
+ const path = dbPath || getDbPath();
1216
+ ensureDir(path);
1217
+ _db = new Database(path);
1218
+ _db.run("PRAGMA journal_mode = WAL");
1219
+ _db.run("PRAGMA busy_timeout = 5000");
1220
+ _db.run("PRAGMA foreign_keys = ON");
1221
+ runMigrations(_db);
1222
+ backfillTaskTags(_db);
1223
+ backfillMachineId(_db);
1224
+ return _db;
1225
+ }
1226
+ function closeDatabase() {
1227
+ if (_db) {
1228
+ _db.close();
1229
+ _db = null;
1230
+ }
1231
+ }
1232
+ function resetDatabase() {
1233
+ _db = null;
1234
+ }
1235
+ function now() {
1236
+ return new Date().toISOString();
1237
+ }
1238
+ function uuid() {
1239
+ return crypto.randomUUID();
1240
+ }
1241
+ function isLockExpired(lockedAt) {
1242
+ if (!lockedAt)
1243
+ return true;
1244
+ const lockTime = new Date(lockedAt).getTime();
1245
+ const expiryMs = LOCK_EXPIRY_MINUTES * 60 * 1000;
1246
+ return Date.now() - lockTime > expiryMs;
1247
+ }
1248
+ function lockExpiryCutoff(nowMs = Date.now()) {
1249
+ const expiryMs = LOCK_EXPIRY_MINUTES * 60 * 1000;
1250
+ return new Date(nowMs - expiryMs).toISOString();
1251
+ }
1252
+ function clearExpiredLocks(db) {
1253
+ const cutoff = lockExpiryCutoff();
1254
+ db.run("UPDATE tasks SET locked_by = NULL, locked_at = NULL WHERE locked_at IS NOT NULL AND locked_at < ?", [cutoff]);
1255
+ }
1256
+ function resolvePartialId(db, table, partialId) {
1257
+ if (partialId.length >= 36) {
1258
+ const row = db.query(`SELECT id FROM ${table} WHERE id = ?`).get(partialId);
1259
+ return row?.id ?? null;
1260
+ }
1261
+ const rows = db.query(`SELECT id FROM ${table} WHERE id LIKE ?`).all(`${partialId}%`);
1262
+ if (rows.length === 1) {
1263
+ return rows[0].id;
1264
+ }
1265
+ if (rows.length > 1) {
1266
+ return null;
1267
+ }
1268
+ if (table === "tasks") {
1269
+ const shortIdRows = db.query("SELECT id FROM tasks WHERE short_id = ?").all(partialId);
1270
+ if (shortIdRows.length === 1) {
1271
+ return shortIdRows[0].id;
1272
+ }
1273
+ }
1274
+ if (table === "task_lists") {
1275
+ const slugRow = db.query("SELECT id FROM task_lists WHERE slug = ?").get(partialId);
1276
+ if (slugRow)
1277
+ return slugRow.id;
1278
+ }
1279
+ if (table === "projects") {
1280
+ const nameRow = db.query("SELECT id FROM projects WHERE lower(name) = ?").get(partialId.toLowerCase());
1281
+ if (nameRow)
1282
+ return nameRow.id;
1283
+ }
1284
+ return null;
1285
+ }
1286
+ var LOCK_EXPIRY_MINUTES = 30, _db = null;
1287
+ var init_database = __esm(() => {
1288
+ init_schema();
1289
+ init_machines();
1290
+ });
1291
+
1099
1292
  // src/types/index.ts
1100
1293
  var TASK_STATUSES, TASK_PRIORITIES, PLAN_STATUSES, VersionConflictError, TaskNotFoundError, ProjectNotFoundError, PlanNotFoundError, LockError, AgentNotFoundError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError, DISPATCH_STATUSES, DispatchNotFoundError;
1101
1294
  var init_types = __esm(() => {
@@ -4595,6 +4788,10 @@ function cleanExpiredLocks(db) {
4595
4788
  const result = d.run("DELETE FROM resource_locks WHERE expires_at < ?", [new Date().toISOString()]);
4596
4789
  return result.changes;
4597
4790
  }
4791
+
4792
+ // src/index.ts
4793
+ init_machines();
4794
+
4598
4795
  // src/db/orgs.ts
4599
4796
  init_database();
4600
4797
  function rowToOrg(row) {
@@ -16764,6 +16961,7 @@ export {
16764
16961
  runDueDispatches,
16765
16962
  resolveVariables,
16766
16963
  resolvePartialId,
16964
+ resetMachineId,
16767
16965
  resetDatabase,
16768
16966
  removeTaskRelationshipByPair,
16769
16967
  removeTaskRelationship,
@@ -16804,6 +17002,7 @@ export {
16804
17002
  listProjectSources,
16805
17003
  listPlans,
16806
17004
  listOrgs,
17005
+ listMachines,
16807
17006
  listHandoffs,
16808
17007
  listDispatches,
16809
17008
  listDispatchLogs,
@@ -16850,7 +17049,11 @@ export {
16850
17049
  getOrgChart,
16851
17050
  getOrgByName,
16852
17051
  getOrg,
17052
+ getOrCreateLocalMachine,
16853
17053
  getNextTask,
17054
+ getMachineId,
17055
+ getMachineByName,
17056
+ getMachine,
16854
17057
  getLeaderboard,
16855
17058
  getLatestSnapshot,
16856
17059
  getLatestHandoff,
@@ -16899,6 +17102,7 @@ export {
16899
17102
  deleteProject,
16900
17103
  deletePlan,
16901
17104
  deleteOrg,
17105
+ deleteMachine,
16902
17106
  deleteComment,
16903
17107
  deleteAgent,
16904
17108
  defaultSyncAgents,
@@ -0,0 +1,11 @@
1
+ import type { SyncPrefer, SyncResult } from "./sync-types.js";
2
+ export declare function pushToAgentTaskList(agent: string, taskListId: string, projectId?: string, options?: {
3
+ prefer?: SyncPrefer;
4
+ }): SyncResult;
5
+ export declare function pullFromAgentTaskList(agent: string, taskListId: string, projectId?: string, options?: {
6
+ prefer?: SyncPrefer;
7
+ }): SyncResult;
8
+ export declare function syncAgentTaskList(agent: string, taskListId: string, projectId?: string, options?: {
9
+ prefer?: SyncPrefer;
10
+ }): SyncResult;
11
+ //# sourceMappingURL=agent-tasks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-tasks.d.ts","sourceRoot":"","sources":["../../src/lib/agent-tasks.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AA+D9D,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,UAAU,CAAA;CAAO,GACpC,UAAU,CAuFZ;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,UAAU,CAAA;CAAO,GACpC,UAAU,CAoFZ;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,UAAU,CAAA;CAAO,GACpC,UAAU,CAQZ"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Auto-assign tasks to agents using Cerebras LLM for intelligent routing.
3
+ * Falls back to capability-based matching when the API key is unavailable or the call fails.
4
+ */
5
+ import type { Database } from "bun:sqlite";
6
+ import type { Task } from "../types/index.js";
7
+ export interface AutoAssignResult {
8
+ task_id: string;
9
+ assigned_to: string | null;
10
+ agent_name: string | null;
11
+ method: "cerebras" | "capability_match" | "no_agents";
12
+ reason?: string;
13
+ }
14
+ /**
15
+ * Find the best agent to assign a task to (legacy simple version, no I/O).
16
+ * Strategy: least-loaded agent with role "agent" (not admin/observer).
17
+ */
18
+ export declare function findBestAgent(_task: Task, db?: Database): string | null;
19
+ /**
20
+ * Auto-assign a task to the best available agent.
21
+ * Uses Cerebras LLM (llama-3.3-70b) if CEREBRAS_API_KEY is set,
22
+ * otherwise falls back to capability-based matching.
23
+ */
24
+ export declare function autoAssignTask(taskId: string, db?: Database): Promise<AutoAssignResult>;
25
+ //# sourceMappingURL=auto-assign.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-assign.d.ts","sourceRoot":"","sources":["../../src/lib/auto-assign.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAI3C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAK9C,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,MAAM,EAAE,UAAU,GAAG,kBAAkB,GAAG,WAAW,CAAC;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CAsBvE;AA8DD;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA8D7F"}
@@ -0,0 +1,18 @@
1
+ import type { Database } from "bun:sqlite";
2
+ export interface BurndownData {
3
+ total: number;
4
+ completed: number;
5
+ remaining: number;
6
+ days: {
7
+ date: string;
8
+ completed_cumulative: number;
9
+ ideal: number;
10
+ }[];
11
+ chart: string;
12
+ }
13
+ export declare function getBurndown(opts: {
14
+ plan_id?: string;
15
+ project_id?: string;
16
+ task_list_id?: string;
17
+ }, db?: Database): BurndownData;
18
+ //# sourceMappingURL=burndown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"burndown.d.ts","sourceRoot":"","sources":["../../src/lib/burndown.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAG3C,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,oBAAoB,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACtE,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,YAAY,CA8C/H"}
@@ -0,0 +1,20 @@
1
+ import type { SyncPrefer, SyncResult } from "./sync-types.js";
2
+ /**
3
+ * Push all SQLite tasks to a Claude Code task list directory.
4
+ */
5
+ export declare function pushToClaudeTaskList(taskListId: string, projectId?: string, options?: {
6
+ prefer?: SyncPrefer;
7
+ }): SyncResult;
8
+ /**
9
+ * Pull tasks from a Claude Code task list into SQLite.
10
+ */
11
+ export declare function pullFromClaudeTaskList(taskListId: string, projectId?: string, options?: {
12
+ prefer?: SyncPrefer;
13
+ }): SyncResult;
14
+ /**
15
+ * Bidirectional sync: pull first, then push.
16
+ */
17
+ export declare function syncClaudeTaskList(taskListId: string, projectId?: string, options?: {
18
+ prefer?: SyncPrefer;
19
+ }): SyncResult;
20
+ //# sourceMappingURL=claude-tasks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-tasks.d.ts","sourceRoot":"","sources":["../../src/lib/claude-tasks.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAwF9D;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,UAAU,CAAA;CAAO,GACpC,UAAU,CA4GZ;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,UAAU,CAAA;CAAO,GACpC,UAAU,CAwFZ;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,UAAU,CAAA;CAAO,GACpC,UAAU,CAQZ"}
@@ -0,0 +1,17 @@
1
+ import type { Database } from "bun:sqlite";
2
+ import type { Task } from "../types/index.js";
3
+ import { type CompletionGuardConfig } from "./config.js";
4
+ /**
5
+ * Checks completion guards before allowing a task to be marked as completed.
6
+ * Throws CompletionGuardError if any guard condition is violated.
7
+ *
8
+ * Guards:
9
+ * 1. Status check — task must be in_progress
10
+ * 2. Minimum work duration — must have spent enough time since startTask()
11
+ * 3. Rate limit — max completions per agent per time window
12
+ * 4. Cooldown — minimum gap between consecutive completions
13
+ *
14
+ * @param configOverride - Optional config override (used in tests)
15
+ */
16
+ export declare function checkCompletionGuard(task: Task, agentId: string | null, db: Database, configOverride?: Required<CompletionGuardConfig>): void;
17
+ //# sourceMappingURL=completion-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"completion-guard.d.ts","sourceRoot":"","sources":["../../src/lib/completion-guard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAE9C,OAAO,EAA4B,KAAK,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAGnF;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,EAAE,EAAE,QAAQ,EACZ,cAAc,CAAC,EAAE,QAAQ,CAAC,qBAAqB,CAAC,GAC/C,IAAI,CAyEN"}