@hasna/todos 0.11.21 → 0.11.23

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.
@@ -1168,6 +1168,12 @@ var init_schema = __esm(() => {
1168
1168
  ALTER TABLE tasks ADD COLUMN archived_at TEXT;
1169
1169
  CREATE INDEX IF NOT EXISTS idx_tasks_archived ON tasks(archived_at) WHERE archived_at IS NOT NULL;
1170
1170
  INSERT OR IGNORE INTO _migrations (id) VALUES (43);
1171
+ `,
1172
+ `
1173
+ DROP INDEX IF EXISTS idx_tasks_short_id;
1174
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_tasks_short_id ON tasks(short_id, machine_id) WHERE short_id IS NOT NULL AND machine_id IS NOT NULL;
1175
+ CREATE INDEX IF NOT EXISTS idx_tasks_short_id_lookup ON tasks(short_id) WHERE short_id IS NOT NULL;
1176
+ INSERT OR IGNORE INTO _migrations (id) VALUES (44);
1171
1177
  `
1172
1178
  ];
1173
1179
  });
@@ -1376,6 +1382,68 @@ var init_database = __esm(() => {
1376
1382
  init_machines();
1377
1383
  });
1378
1384
 
1385
+ // src/lib/sync-utils.ts
1386
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
1387
+ function readJsonFile(path) {
1388
+ try {
1389
+ return JSON.parse(readFileSync(path, "utf-8"));
1390
+ } catch {
1391
+ return null;
1392
+ }
1393
+ }
1394
+ var HOME;
1395
+ var init_sync_utils = __esm(() => {
1396
+ HOME = process.env["HOME"] || process.env["USERPROFILE"] || "~";
1397
+ });
1398
+
1399
+ // src/lib/config.ts
1400
+ import { existsSync as existsSync3 } from "fs";
1401
+ import { join as join2 } from "path";
1402
+ function getTodosGlobalDir() {
1403
+ const home = process.env["HOME"] || HOME;
1404
+ const newDir = join2(home, ".hasna", "todos");
1405
+ const legacyDir = join2(home, ".todos");
1406
+ if (!existsSync3(newDir) && existsSync3(legacyDir))
1407
+ return legacyDir;
1408
+ return newDir;
1409
+ }
1410
+ function getConfigPath() {
1411
+ return join2(getTodosGlobalDir(), "config.json");
1412
+ }
1413
+ function loadConfig() {
1414
+ if (cached)
1415
+ return cached;
1416
+ if (!existsSync3(getConfigPath())) {
1417
+ cached = {};
1418
+ return cached;
1419
+ }
1420
+ const config = readJsonFile(getConfigPath()) || {};
1421
+ if (typeof config.sync_agents === "string") {
1422
+ config.sync_agents = config.sync_agents.split(",").map((a) => a.trim()).filter(Boolean);
1423
+ }
1424
+ cached = config;
1425
+ return cached;
1426
+ }
1427
+ function getCompletionGuardConfig(projectPath) {
1428
+ const config = loadConfig();
1429
+ const global = { ...GUARD_DEFAULTS, ...config.completion_guard };
1430
+ if (projectPath && config.project_overrides?.[projectPath]?.completion_guard) {
1431
+ return { ...global, ...config.project_overrides[projectPath].completion_guard };
1432
+ }
1433
+ return global;
1434
+ }
1435
+ var cached = null, GUARD_DEFAULTS;
1436
+ var init_config = __esm(() => {
1437
+ init_sync_utils();
1438
+ GUARD_DEFAULTS = {
1439
+ enabled: false,
1440
+ min_work_seconds: 30,
1441
+ max_completions_per_window: 5,
1442
+ window_minutes: 10,
1443
+ cooldown_seconds: 60
1444
+ };
1445
+ });
1446
+
1379
1447
  // src/db/projects.ts
1380
1448
  var exports_projects = {};
1381
1449
  __export(exports_projects, {
@@ -1557,13 +1625,6 @@ function nextTaskShortId(projectId, db) {
1557
1625
  const project = getProject(projectId, d);
1558
1626
  if (!project || !project.task_prefix)
1559
1627
  return null;
1560
- const prefix = project.task_prefix;
1561
- const prefixLen = prefix.length + 2;
1562
- const maxRow = d.query(`SELECT MAX(CAST(SUBSTR(short_id, ?) AS INTEGER)) as max_counter FROM tasks WHERE short_id LIKE ?`).get(prefixLen, `${prefix}-%`);
1563
- const syncedMax = maxRow?.max_counter ?? 0;
1564
- if (syncedMax >= (project.task_counter ?? 0)) {
1565
- d.run("UPDATE projects SET task_counter = ?, updated_at = ? WHERE id = ?", [syncedMax, now(), projectId]);
1566
- }
1567
1628
  d.run("UPDATE projects SET task_counter = task_counter + 1, updated_at = ? WHERE id = ?", [now(), projectId]);
1568
1629
  const updated = getProject(projectId, d);
1569
1630
  const padded = String(updated.task_counter).padStart(5, "0");
@@ -1627,68 +1688,6 @@ var init_projects = __esm(() => {
1627
1688
  init_machines();
1628
1689
  });
1629
1690
 
1630
- // src/lib/sync-utils.ts
1631
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
1632
- function readJsonFile(path) {
1633
- try {
1634
- return JSON.parse(readFileSync(path, "utf-8"));
1635
- } catch {
1636
- return null;
1637
- }
1638
- }
1639
- var HOME;
1640
- var init_sync_utils = __esm(() => {
1641
- HOME = process.env["HOME"] || process.env["USERPROFILE"] || "~";
1642
- });
1643
-
1644
- // src/lib/config.ts
1645
- import { existsSync as existsSync3 } from "fs";
1646
- import { join as join2 } from "path";
1647
- function getTodosGlobalDir() {
1648
- const home = process.env["HOME"] || HOME;
1649
- const newDir = join2(home, ".hasna", "todos");
1650
- const legacyDir = join2(home, ".todos");
1651
- if (!existsSync3(newDir) && existsSync3(legacyDir))
1652
- return legacyDir;
1653
- return newDir;
1654
- }
1655
- function getConfigPath() {
1656
- return join2(getTodosGlobalDir(), "config.json");
1657
- }
1658
- function loadConfig() {
1659
- if (cached)
1660
- return cached;
1661
- if (!existsSync3(getConfigPath())) {
1662
- cached = {};
1663
- return cached;
1664
- }
1665
- const config = readJsonFile(getConfigPath()) || {};
1666
- if (typeof config.sync_agents === "string") {
1667
- config.sync_agents = config.sync_agents.split(",").map((a) => a.trim()).filter(Boolean);
1668
- }
1669
- cached = config;
1670
- return cached;
1671
- }
1672
- function getCompletionGuardConfig(projectPath) {
1673
- const config = loadConfig();
1674
- const global = { ...GUARD_DEFAULTS, ...config.completion_guard };
1675
- if (projectPath && config.project_overrides?.[projectPath]?.completion_guard) {
1676
- return { ...global, ...config.project_overrides[projectPath].completion_guard };
1677
- }
1678
- return global;
1679
- }
1680
- var cached = null, GUARD_DEFAULTS;
1681
- var init_config = __esm(() => {
1682
- init_sync_utils();
1683
- GUARD_DEFAULTS = {
1684
- enabled: false,
1685
- min_work_seconds: 30,
1686
- max_completions_per_window: 5,
1687
- window_minutes: 10,
1688
- cooldown_seconds: 60
1689
- };
1690
- });
1691
-
1692
1691
  // src/lib/completion-guard.ts
1693
1692
  function checkCompletionGuard(task, agentId, db, configOverride) {
1694
1693
  let config;
@@ -2534,47 +2533,56 @@ function replaceTaskTags(taskId, tags, db) {
2534
2533
  }
2535
2534
  function createTask(input, db) {
2536
2535
  const d = db || getDatabase();
2537
- const id = uuid();
2538
2536
  const timestamp = now();
2539
2537
  const tags = input.tags || [];
2540
- const shortId = input.project_id ? nextTaskShortId(input.project_id, d) : null;
2541
- const title = shortId ? `${shortId}: ${input.title}` : input.title;
2542
2538
  const assignedBy = input.assigned_by || input.agent_id;
2543
2539
  const assignedFromProject = input.assigned_from_project || null;
2544
- d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at, due_at, estimated_minutes, requires_approval, approved_by, approved_at, recurrence_rule, recurrence_parent_id, spawns_template_id, reason, spawned_from_session, assigned_by, assigned_from_project, task_type)
2545
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
2546
- id,
2547
- shortId,
2548
- input.project_id || null,
2549
- input.parent_id || null,
2550
- input.plan_id || null,
2551
- input.task_list_id || null,
2552
- title,
2553
- input.description || null,
2554
- input.status || "pending",
2555
- input.priority || "medium",
2556
- input.agent_id || null,
2557
- input.assigned_to || null,
2558
- input.session_id || null,
2559
- input.working_dir || null,
2560
- JSON.stringify(tags),
2561
- JSON.stringify(input.metadata || {}),
2562
- timestamp,
2563
- timestamp,
2564
- input.due_at || null,
2565
- input.estimated_minutes || null,
2566
- input.requires_approval ? 1 : 0,
2567
- null,
2568
- null,
2569
- input.recurrence_rule || null,
2570
- input.recurrence_parent_id || null,
2571
- input.spawns_template_id || null,
2572
- input.reason || null,
2573
- input.spawned_from_session || null,
2574
- assignedBy || null,
2575
- assignedFromProject || null,
2576
- input.task_type || null
2577
- ]);
2540
+ let id = uuid();
2541
+ for (let attempt = 0;attempt < 3; attempt++) {
2542
+ try {
2543
+ d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at, due_at, estimated_minutes, requires_approval, approved_by, approved_at, recurrence_rule, recurrence_parent_id, spawns_template_id, reason, spawned_from_session, assigned_by, assigned_from_project, task_type)
2544
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
2545
+ id,
2546
+ null,
2547
+ input.project_id || null,
2548
+ input.parent_id || null,
2549
+ input.plan_id || null,
2550
+ input.task_list_id || null,
2551
+ input.title,
2552
+ input.description || null,
2553
+ input.status || "pending",
2554
+ input.priority || "medium",
2555
+ input.agent_id || null,
2556
+ input.assigned_to || null,
2557
+ input.session_id || null,
2558
+ input.working_dir || null,
2559
+ JSON.stringify(tags),
2560
+ JSON.stringify(input.metadata || {}),
2561
+ timestamp,
2562
+ timestamp,
2563
+ input.due_at || null,
2564
+ input.estimated_minutes || null,
2565
+ input.requires_approval ? 1 : 0,
2566
+ null,
2567
+ null,
2568
+ input.recurrence_rule || null,
2569
+ input.recurrence_parent_id || null,
2570
+ input.spawns_template_id || null,
2571
+ input.reason || null,
2572
+ input.spawned_from_session || null,
2573
+ assignedBy || null,
2574
+ assignedFromProject || null,
2575
+ input.task_type || null
2576
+ ]);
2577
+ break;
2578
+ } catch (e) {
2579
+ if (attempt < 2 && e?.message?.includes("UNIQUE constraint failed: tasks.id")) {
2580
+ id = uuid();
2581
+ continue;
2582
+ }
2583
+ throw e;
2584
+ }
2585
+ }
2578
2586
  if (tags.length > 0) {
2579
2587
  insertTaskTags(id, tags, d);
2580
2588
  }
@@ -3660,7 +3668,6 @@ function getOverdueTasks(projectId, db) {
3660
3668
  var init_tasks = __esm(() => {
3661
3669
  init_types();
3662
3670
  init_database();
3663
- init_projects();
3664
3671
  init_completion_guard();
3665
3672
  init_audit();
3666
3673
  init_recurrence();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/todos",
3
- "version": "0.11.21",
3
+ "version": "0.11.23",
4
4
  "description": "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",