@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.
package/dist/mcp/index.js CHANGED
@@ -1105,6 +1105,12 @@ var init_schema = __esm(() => {
1105
1105
  ALTER TABLE tasks ADD COLUMN archived_at TEXT;
1106
1106
  CREATE INDEX IF NOT EXISTS idx_tasks_archived ON tasks(archived_at) WHERE archived_at IS NOT NULL;
1107
1107
  INSERT OR IGNORE INTO _migrations (id) VALUES (43);
1108
+ `,
1109
+ `
1110
+ DROP INDEX IF EXISTS idx_tasks_short_id;
1111
+ 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;
1112
+ CREATE INDEX IF NOT EXISTS idx_tasks_short_id_lookup ON tasks(short_id) WHERE short_id IS NOT NULL;
1113
+ INSERT OR IGNORE INTO _migrations (id) VALUES (44);
1108
1114
  `
1109
1115
  ];
1110
1116
  });
@@ -12533,159 +12539,6 @@ var init_types2 = __esm(() => {
12533
12539
  };
12534
12540
  });
12535
12541
 
12536
- // src/db/projects.ts
12537
- function slugify(name) {
12538
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
12539
- }
12540
- function generatePrefix(name, db) {
12541
- const words = name.replace(/[^a-zA-Z0-9\s]/g, "").trim().split(/\s+/);
12542
- let prefix;
12543
- if (words.length >= 3) {
12544
- prefix = words.slice(0, 3).map((w) => w[0].toUpperCase()).join("");
12545
- } else if (words.length === 2) {
12546
- prefix = (words[0].slice(0, 2) + words[1][0]).toUpperCase();
12547
- } else {
12548
- prefix = words[0].slice(0, 3).toUpperCase();
12549
- }
12550
- let candidate = prefix;
12551
- let suffix = 1;
12552
- while (true) {
12553
- const existing = db.query("SELECT id FROM projects WHERE task_prefix = ?").get(candidate);
12554
- if (!existing)
12555
- return candidate;
12556
- suffix++;
12557
- candidate = `${prefix}${suffix}`;
12558
- }
12559
- }
12560
- function createProject(input, db) {
12561
- const d = db || getDatabase();
12562
- const id = uuid();
12563
- const timestamp = now();
12564
- const taskListId = input.task_list_id ?? `todos-${slugify(input.name)}`;
12565
- const taskPrefix = input.task_prefix || generatePrefix(input.name, d);
12566
- d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, created_at, updated_at)
12567
- VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix, timestamp, timestamp]);
12568
- return getProject(id, d);
12569
- }
12570
- function getProject(id, db) {
12571
- const d = db || getDatabase();
12572
- const row = d.query("SELECT * FROM projects WHERE id = ?").get(id);
12573
- return row;
12574
- }
12575
- function listProjects(db) {
12576
- const d = db || getDatabase();
12577
- return d.query("SELECT * FROM projects ORDER BY name").all();
12578
- }
12579
- function updateProject(id, input, db) {
12580
- const d = db || getDatabase();
12581
- const project = getProject(id, d);
12582
- if (!project)
12583
- throw new ProjectNotFoundError(id);
12584
- const sets = ["updated_at = ?"];
12585
- const params = [now()];
12586
- if (input.name !== undefined) {
12587
- sets.push("name = ?");
12588
- params.push(input.name);
12589
- }
12590
- if (input.description !== undefined) {
12591
- sets.push("description = ?");
12592
- params.push(input.description);
12593
- }
12594
- if (input.task_list_id !== undefined) {
12595
- sets.push("task_list_id = ?");
12596
- params.push(input.task_list_id);
12597
- }
12598
- if (input.path !== undefined) {
12599
- sets.push("path = ?");
12600
- params.push(input.path);
12601
- }
12602
- params.push(id);
12603
- d.run(`UPDATE projects SET ${sets.join(", ")} WHERE id = ?`, params);
12604
- return getProject(id, d);
12605
- }
12606
- function renameProject(id, input, db) {
12607
- const d = db || getDatabase();
12608
- const project = getProject(id, d);
12609
- if (!project)
12610
- throw new ProjectNotFoundError(id);
12611
- let taskListsUpdated = 0;
12612
- const ts = now();
12613
- if (input.new_slug !== undefined) {
12614
- const normalised = input.new_slug.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-|-$/g, "");
12615
- if (!normalised)
12616
- throw new Error("Invalid slug \u2014 must be non-empty kebab-case");
12617
- const conflict = d.query("SELECT id FROM projects WHERE task_list_id = ? AND id != ?").get(normalised, id);
12618
- if (conflict)
12619
- throw new Error(`Slug "${normalised}" is already used by another project`);
12620
- const oldSlug = project.task_list_id;
12621
- d.run("UPDATE projects SET task_list_id = ?, updated_at = ? WHERE id = ?", [normalised, ts, id]);
12622
- if (oldSlug) {
12623
- const result = d.run("UPDATE task_lists SET slug = ?, name = COALESCE(?, name), updated_at = ? WHERE project_id = ? AND slug = ?", [normalised, input.name ?? null, ts, id, oldSlug]);
12624
- taskListsUpdated = result.changes;
12625
- }
12626
- }
12627
- if (input.name !== undefined) {
12628
- d.run("UPDATE projects SET name = ?, updated_at = ? WHERE id = ?", [input.name, ts, id]);
12629
- }
12630
- return { project: getProject(id, d), task_lists_updated: taskListsUpdated };
12631
- }
12632
- function rowToSource(row) {
12633
- return {
12634
- ...row,
12635
- metadata: row.metadata ? JSON.parse(row.metadata) : {}
12636
- };
12637
- }
12638
- function addProjectSource(input, db) {
12639
- const d = db || getDatabase();
12640
- const id = uuid();
12641
- const timestamp = now();
12642
- d.run(`INSERT INTO project_sources (id, project_id, type, name, uri, description, metadata, created_at, updated_at)
12643
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
12644
- id,
12645
- input.project_id,
12646
- input.type,
12647
- input.name,
12648
- input.uri,
12649
- input.description || null,
12650
- JSON.stringify(input.metadata || {}),
12651
- timestamp,
12652
- timestamp
12653
- ]);
12654
- return rowToSource(d.query("SELECT * FROM project_sources WHERE id = ?").get(id));
12655
- }
12656
- function removeProjectSource(id, db) {
12657
- const d = db || getDatabase();
12658
- const result = d.run("DELETE FROM project_sources WHERE id = ?", [id]);
12659
- return result.changes > 0;
12660
- }
12661
- function listProjectSources(projectId, db) {
12662
- const d = db || getDatabase();
12663
- const rows = d.query("SELECT * FROM project_sources WHERE project_id = ? ORDER BY name").all(projectId);
12664
- return rows.map(rowToSource);
12665
- }
12666
- function nextTaskShortId(projectId, db) {
12667
- const d = db || getDatabase();
12668
- const project = getProject(projectId, d);
12669
- if (!project || !project.task_prefix)
12670
- return null;
12671
- const prefix = project.task_prefix;
12672
- const prefixLen = prefix.length + 2;
12673
- const maxRow = d.query(`SELECT MAX(CAST(SUBSTR(short_id, ?) AS INTEGER)) as max_counter FROM tasks WHERE short_id LIKE ?`).get(prefixLen, `${prefix}-%`);
12674
- const syncedMax = maxRow?.max_counter ?? 0;
12675
- if (syncedMax >= (project.task_counter ?? 0)) {
12676
- d.run("UPDATE projects SET task_counter = ?, updated_at = ? WHERE id = ?", [syncedMax, now(), projectId]);
12677
- }
12678
- d.run("UPDATE projects SET task_counter = task_counter + 1, updated_at = ? WHERE id = ?", [now(), projectId]);
12679
- const updated = getProject(projectId, d);
12680
- const padded = String(updated.task_counter).padStart(5, "0");
12681
- return `${updated.task_prefix}-${padded}`;
12682
- }
12683
- var init_projects = __esm(() => {
12684
- init_types2();
12685
- init_database();
12686
- init_machines();
12687
- });
12688
-
12689
12542
  // src/lib/sync-utils.ts
12690
12543
  import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync3, readdirSync as readdirSync4, statSync, writeFileSync as writeFileSync3 } from "fs";
12691
12544
  import { join as join7 } from "path";
@@ -12831,6 +12684,142 @@ var init_config2 = __esm(() => {
12831
12684
  };
12832
12685
  });
12833
12686
 
12687
+ // src/db/projects.ts
12688
+ function slugify(name) {
12689
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
12690
+ }
12691
+ function generatePrefix(name, db) {
12692
+ const words = name.replace(/[^a-zA-Z0-9\s]/g, "").trim().split(/\s+/);
12693
+ let prefix;
12694
+ if (words.length >= 3) {
12695
+ prefix = words.slice(0, 3).map((w) => w[0].toUpperCase()).join("");
12696
+ } else if (words.length === 2) {
12697
+ prefix = (words[0].slice(0, 2) + words[1][0]).toUpperCase();
12698
+ } else {
12699
+ prefix = words[0].slice(0, 3).toUpperCase();
12700
+ }
12701
+ let candidate = prefix;
12702
+ let suffix = 1;
12703
+ while (true) {
12704
+ const existing = db.query("SELECT id FROM projects WHERE task_prefix = ?").get(candidate);
12705
+ if (!existing)
12706
+ return candidate;
12707
+ suffix++;
12708
+ candidate = `${prefix}${suffix}`;
12709
+ }
12710
+ }
12711
+ function createProject(input, db) {
12712
+ const d = db || getDatabase();
12713
+ const id = uuid();
12714
+ const timestamp = now();
12715
+ const taskListId = input.task_list_id ?? `todos-${slugify(input.name)}`;
12716
+ const taskPrefix = input.task_prefix || generatePrefix(input.name, d);
12717
+ d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, created_at, updated_at)
12718
+ VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix, timestamp, timestamp]);
12719
+ return getProject(id, d);
12720
+ }
12721
+ function getProject(id, db) {
12722
+ const d = db || getDatabase();
12723
+ const row = d.query("SELECT * FROM projects WHERE id = ?").get(id);
12724
+ return row;
12725
+ }
12726
+ function listProjects(db) {
12727
+ const d = db || getDatabase();
12728
+ return d.query("SELECT * FROM projects ORDER BY name").all();
12729
+ }
12730
+ function updateProject(id, input, db) {
12731
+ const d = db || getDatabase();
12732
+ const project = getProject(id, d);
12733
+ if (!project)
12734
+ throw new ProjectNotFoundError(id);
12735
+ const sets = ["updated_at = ?"];
12736
+ const params = [now()];
12737
+ if (input.name !== undefined) {
12738
+ sets.push("name = ?");
12739
+ params.push(input.name);
12740
+ }
12741
+ if (input.description !== undefined) {
12742
+ sets.push("description = ?");
12743
+ params.push(input.description);
12744
+ }
12745
+ if (input.task_list_id !== undefined) {
12746
+ sets.push("task_list_id = ?");
12747
+ params.push(input.task_list_id);
12748
+ }
12749
+ if (input.path !== undefined) {
12750
+ sets.push("path = ?");
12751
+ params.push(input.path);
12752
+ }
12753
+ params.push(id);
12754
+ d.run(`UPDATE projects SET ${sets.join(", ")} WHERE id = ?`, params);
12755
+ return getProject(id, d);
12756
+ }
12757
+ function renameProject(id, input, db) {
12758
+ const d = db || getDatabase();
12759
+ const project = getProject(id, d);
12760
+ if (!project)
12761
+ throw new ProjectNotFoundError(id);
12762
+ let taskListsUpdated = 0;
12763
+ const ts = now();
12764
+ if (input.new_slug !== undefined) {
12765
+ const normalised = input.new_slug.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-|-$/g, "");
12766
+ if (!normalised)
12767
+ throw new Error("Invalid slug \u2014 must be non-empty kebab-case");
12768
+ const conflict = d.query("SELECT id FROM projects WHERE task_list_id = ? AND id != ?").get(normalised, id);
12769
+ if (conflict)
12770
+ throw new Error(`Slug "${normalised}" is already used by another project`);
12771
+ const oldSlug = project.task_list_id;
12772
+ d.run("UPDATE projects SET task_list_id = ?, updated_at = ? WHERE id = ?", [normalised, ts, id]);
12773
+ if (oldSlug) {
12774
+ const result = d.run("UPDATE task_lists SET slug = ?, name = COALESCE(?, name), updated_at = ? WHERE project_id = ? AND slug = ?", [normalised, input.name ?? null, ts, id, oldSlug]);
12775
+ taskListsUpdated = result.changes;
12776
+ }
12777
+ }
12778
+ if (input.name !== undefined) {
12779
+ d.run("UPDATE projects SET name = ?, updated_at = ? WHERE id = ?", [input.name, ts, id]);
12780
+ }
12781
+ return { project: getProject(id, d), task_lists_updated: taskListsUpdated };
12782
+ }
12783
+ function rowToSource(row) {
12784
+ return {
12785
+ ...row,
12786
+ metadata: row.metadata ? JSON.parse(row.metadata) : {}
12787
+ };
12788
+ }
12789
+ function addProjectSource(input, db) {
12790
+ const d = db || getDatabase();
12791
+ const id = uuid();
12792
+ const timestamp = now();
12793
+ d.run(`INSERT INTO project_sources (id, project_id, type, name, uri, description, metadata, created_at, updated_at)
12794
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
12795
+ id,
12796
+ input.project_id,
12797
+ input.type,
12798
+ input.name,
12799
+ input.uri,
12800
+ input.description || null,
12801
+ JSON.stringify(input.metadata || {}),
12802
+ timestamp,
12803
+ timestamp
12804
+ ]);
12805
+ return rowToSource(d.query("SELECT * FROM project_sources WHERE id = ?").get(id));
12806
+ }
12807
+ function removeProjectSource(id, db) {
12808
+ const d = db || getDatabase();
12809
+ const result = d.run("DELETE FROM project_sources WHERE id = ?", [id]);
12810
+ return result.changes > 0;
12811
+ }
12812
+ function listProjectSources(projectId, db) {
12813
+ const d = db || getDatabase();
12814
+ const rows = d.query("SELECT * FROM project_sources WHERE project_id = ? ORDER BY name").all(projectId);
12815
+ return rows.map(rowToSource);
12816
+ }
12817
+ var init_projects = __esm(() => {
12818
+ init_types2();
12819
+ init_database();
12820
+ init_machines();
12821
+ });
12822
+
12834
12823
  // src/lib/completion-guard.ts
12835
12824
  function checkCompletionGuard(task, agentId, db, configOverride) {
12836
12825
  let config;
@@ -13709,47 +13698,56 @@ function replaceTaskTags(taskId, tags, db) {
13709
13698
  }
13710
13699
  function createTask(input, db) {
13711
13700
  const d = db || getDatabase();
13712
- const id = uuid();
13713
13701
  const timestamp = now();
13714
13702
  const tags = input.tags || [];
13715
- const shortId = input.project_id ? nextTaskShortId(input.project_id, d) : null;
13716
- const title = shortId ? `${shortId}: ${input.title}` : input.title;
13717
13703
  const assignedBy = input.assigned_by || input.agent_id;
13718
13704
  const assignedFromProject = input.assigned_from_project || null;
13719
- 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)
13720
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
13721
- id,
13722
- shortId,
13723
- input.project_id || null,
13724
- input.parent_id || null,
13725
- input.plan_id || null,
13726
- input.task_list_id || null,
13727
- title,
13728
- input.description || null,
13729
- input.status || "pending",
13730
- input.priority || "medium",
13731
- input.agent_id || null,
13732
- input.assigned_to || null,
13733
- input.session_id || null,
13734
- input.working_dir || null,
13735
- JSON.stringify(tags),
13736
- JSON.stringify(input.metadata || {}),
13737
- timestamp,
13738
- timestamp,
13739
- input.due_at || null,
13740
- input.estimated_minutes || null,
13741
- input.requires_approval ? 1 : 0,
13742
- null,
13743
- null,
13744
- input.recurrence_rule || null,
13745
- input.recurrence_parent_id || null,
13746
- input.spawns_template_id || null,
13747
- input.reason || null,
13748
- input.spawned_from_session || null,
13749
- assignedBy || null,
13750
- assignedFromProject || null,
13751
- input.task_type || null
13752
- ]);
13705
+ let id = uuid();
13706
+ for (let attempt = 0;attempt < 3; attempt++) {
13707
+ try {
13708
+ 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)
13709
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
13710
+ id,
13711
+ null,
13712
+ input.project_id || null,
13713
+ input.parent_id || null,
13714
+ input.plan_id || null,
13715
+ input.task_list_id || null,
13716
+ input.title,
13717
+ input.description || null,
13718
+ input.status || "pending",
13719
+ input.priority || "medium",
13720
+ input.agent_id || null,
13721
+ input.assigned_to || null,
13722
+ input.session_id || null,
13723
+ input.working_dir || null,
13724
+ JSON.stringify(tags),
13725
+ JSON.stringify(input.metadata || {}),
13726
+ timestamp,
13727
+ timestamp,
13728
+ input.due_at || null,
13729
+ input.estimated_minutes || null,
13730
+ input.requires_approval ? 1 : 0,
13731
+ null,
13732
+ null,
13733
+ input.recurrence_rule || null,
13734
+ input.recurrence_parent_id || null,
13735
+ input.spawns_template_id || null,
13736
+ input.reason || null,
13737
+ input.spawned_from_session || null,
13738
+ assignedBy || null,
13739
+ assignedFromProject || null,
13740
+ input.task_type || null
13741
+ ]);
13742
+ break;
13743
+ } catch (e) {
13744
+ if (attempt < 2 && e?.message?.includes("UNIQUE constraint failed: tasks.id")) {
13745
+ id = uuid();
13746
+ continue;
13747
+ }
13748
+ throw e;
13749
+ }
13750
+ }
13753
13751
  if (tags.length > 0) {
13754
13752
  insertTaskTags(id, tags, d);
13755
13753
  }
@@ -14835,7 +14833,6 @@ function getOverdueTasks(projectId, db) {
14835
14833
  var init_tasks = __esm(() => {
14836
14834
  init_types2();
14837
14835
  init_database();
14838
- init_projects();
14839
14836
  init_completion_guard();
14840
14837
  init_audit();
14841
14838
  init_recurrence();
@@ -17819,6 +17816,12 @@ var init_pg_migrations = __esm(() => {
17819
17816
  CREATE INDEX IF NOT EXISTS idx_template_versions_template ON template_versions(template_id);
17820
17817
 
17821
17818
  INSERT INTO _migrations (id) VALUES (39) ON CONFLICT DO NOTHING;
17819
+ `,
17820
+ `
17821
+ DROP INDEX IF EXISTS idx_tasks_short_id;
17822
+ 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;
17823
+ CREATE INDEX IF NOT EXISTS idx_tasks_short_id_lookup ON tasks(short_id) WHERE short_id IS NOT NULL;
17824
+ INSERT INTO _migrations (id) VALUES (40) ON CONFLICT DO NOTHING;
17822
17825
  `
17823
17826
  ];
17824
17827
  });