@hasna/todos 0.9.2 → 0.9.4

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
@@ -2154,6 +2154,13 @@ function ensureTableMigrations(db) {
2154
2154
  db.exec(MIGRATIONS[4]);
2155
2155
  }
2156
2156
  } catch {}
2157
+ try {
2158
+ db.query("SELECT task_prefix FROM projects LIMIT 0").get();
2159
+ } catch {
2160
+ try {
2161
+ db.exec(MIGRATIONS[5]);
2162
+ } catch {}
2163
+ }
2157
2164
  }
2158
2165
  function backfillTaskTags(db) {
2159
2166
  try {
@@ -2358,6 +2365,15 @@ var init_database = __esm(() => {
2358
2365
  CREATE INDEX IF NOT EXISTS idx_tasks_task_list ON tasks(task_list_id);
2359
2366
 
2360
2367
  INSERT OR IGNORE INTO _migrations (id) VALUES (5);
2368
+ `,
2369
+ `
2370
+ ALTER TABLE projects ADD COLUMN task_prefix TEXT;
2371
+ ALTER TABLE projects ADD COLUMN task_counter INTEGER NOT NULL DEFAULT 0;
2372
+
2373
+ ALTER TABLE tasks ADD COLUMN short_id TEXT;
2374
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_tasks_short_id ON tasks(short_id) WHERE short_id IS NOT NULL;
2375
+
2376
+ INSERT OR IGNORE INTO _migrations (id) VALUES (6);
2361
2377
  `
2362
2378
  ];
2363
2379
  });
@@ -2431,6 +2447,99 @@ var init_types = __esm(() => {
2431
2447
  };
2432
2448
  });
2433
2449
 
2450
+ // src/db/projects.ts
2451
+ function slugify(name) {
2452
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
2453
+ }
2454
+ function generatePrefix(name, db) {
2455
+ const words = name.replace(/[^a-zA-Z0-9\s]/g, "").trim().split(/\s+/);
2456
+ let prefix;
2457
+ if (words.length >= 3) {
2458
+ prefix = words.slice(0, 3).map((w) => w[0].toUpperCase()).join("");
2459
+ } else if (words.length === 2) {
2460
+ prefix = (words[0].slice(0, 2) + words[1][0]).toUpperCase();
2461
+ } else {
2462
+ prefix = words[0].slice(0, 3).toUpperCase();
2463
+ }
2464
+ let candidate = prefix;
2465
+ let suffix = 1;
2466
+ while (true) {
2467
+ const existing = db.query("SELECT id FROM projects WHERE task_prefix = ?").get(candidate);
2468
+ if (!existing)
2469
+ return candidate;
2470
+ suffix++;
2471
+ candidate = `${prefix}${suffix}`;
2472
+ }
2473
+ }
2474
+ function createProject(input, db) {
2475
+ const d = db || getDatabase();
2476
+ const id = uuid();
2477
+ const timestamp = now();
2478
+ const taskListId = input.task_list_id ?? `todos-${slugify(input.name)}`;
2479
+ const taskPrefix = input.task_prefix || generatePrefix(input.name, d);
2480
+ d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, created_at, updated_at)
2481
+ VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix, timestamp, timestamp]);
2482
+ return getProject(id, d);
2483
+ }
2484
+ function getProject(id, db) {
2485
+ const d = db || getDatabase();
2486
+ const row = d.query("SELECT * FROM projects WHERE id = ?").get(id);
2487
+ return row;
2488
+ }
2489
+ function getProjectByPath(path, db) {
2490
+ const d = db || getDatabase();
2491
+ const row = d.query("SELECT * FROM projects WHERE path = ?").get(path);
2492
+ return row;
2493
+ }
2494
+ function listProjects(db) {
2495
+ const d = db || getDatabase();
2496
+ return d.query("SELECT * FROM projects ORDER BY name").all();
2497
+ }
2498
+ function updateProject(id, input, db) {
2499
+ const d = db || getDatabase();
2500
+ const project = getProject(id, d);
2501
+ if (!project)
2502
+ throw new ProjectNotFoundError(id);
2503
+ const sets = ["updated_at = ?"];
2504
+ const params = [now()];
2505
+ if (input.name !== undefined) {
2506
+ sets.push("name = ?");
2507
+ params.push(input.name);
2508
+ }
2509
+ if (input.description !== undefined) {
2510
+ sets.push("description = ?");
2511
+ params.push(input.description);
2512
+ }
2513
+ if (input.task_list_id !== undefined) {
2514
+ sets.push("task_list_id = ?");
2515
+ params.push(input.task_list_id);
2516
+ }
2517
+ params.push(id);
2518
+ d.run(`UPDATE projects SET ${sets.join(", ")} WHERE id = ?`, params);
2519
+ return getProject(id, d);
2520
+ }
2521
+ function nextTaskShortId(projectId, db) {
2522
+ const d = db || getDatabase();
2523
+ const project = getProject(projectId, d);
2524
+ if (!project || !project.task_prefix)
2525
+ return null;
2526
+ d.run("UPDATE projects SET task_counter = task_counter + 1, updated_at = ? WHERE id = ?", [now(), projectId]);
2527
+ const updated = getProject(projectId, d);
2528
+ const padded = String(updated.task_counter).padStart(5, "0");
2529
+ return `${updated.task_prefix}-${padded}`;
2530
+ }
2531
+ function ensureProject(name, path, db) {
2532
+ const d = db || getDatabase();
2533
+ const existing = getProjectByPath(path, d);
2534
+ if (existing)
2535
+ return existing;
2536
+ return createProject({ name, path }, d);
2537
+ }
2538
+ var init_projects = __esm(() => {
2539
+ init_types();
2540
+ init_database();
2541
+ });
2542
+
2434
2543
  // src/db/tasks.ts
2435
2544
  function rowToTask(row) {
2436
2545
  return {
@@ -2459,14 +2568,17 @@ function createTask(input, db) {
2459
2568
  const id = uuid();
2460
2569
  const timestamp = now();
2461
2570
  const tags = input.tags || [];
2462
- d.run(`INSERT INTO tasks (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)
2463
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?)`, [
2571
+ const shortId = input.project_id ? nextTaskShortId(input.project_id, d) : null;
2572
+ const title = shortId ? `${shortId}: ${input.title}` : input.title;
2573
+ 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)
2574
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?)`, [
2464
2575
  id,
2576
+ shortId,
2465
2577
  input.project_id || null,
2466
2578
  input.parent_id || null,
2467
2579
  input.plan_id || null,
2468
2580
  input.task_list_id || null,
2469
- input.title,
2581
+ title,
2470
2582
  input.description || null,
2471
2583
  input.status || "pending",
2472
2584
  input.priority || "medium",
@@ -2757,68 +2869,7 @@ function wouldCreateCycle(taskId, dependsOn, db) {
2757
2869
  var init_tasks = __esm(() => {
2758
2870
  init_types();
2759
2871
  init_database();
2760
- });
2761
-
2762
- // src/db/projects.ts
2763
- function slugify(name) {
2764
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
2765
- }
2766
- function createProject(input, db) {
2767
- const d = db || getDatabase();
2768
- const id = uuid();
2769
- const timestamp = now();
2770
- const taskListId = input.task_list_id ?? `todos-${slugify(input.name)}`;
2771
- d.run(`INSERT INTO projects (id, name, path, description, task_list_id, created_at, updated_at)
2772
- VALUES (?, ?, ?, ?, ?, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, timestamp, timestamp]);
2773
- return getProject(id, d);
2774
- }
2775
- function getProject(id, db) {
2776
- const d = db || getDatabase();
2777
- const row = d.query("SELECT * FROM projects WHERE id = ?").get(id);
2778
- return row;
2779
- }
2780
- function getProjectByPath(path, db) {
2781
- const d = db || getDatabase();
2782
- const row = d.query("SELECT * FROM projects WHERE path = ?").get(path);
2783
- return row;
2784
- }
2785
- function listProjects(db) {
2786
- const d = db || getDatabase();
2787
- return d.query("SELECT * FROM projects ORDER BY name").all();
2788
- }
2789
- function updateProject(id, input, db) {
2790
- const d = db || getDatabase();
2791
- const project = getProject(id, d);
2792
- if (!project)
2793
- throw new ProjectNotFoundError(id);
2794
- const sets = ["updated_at = ?"];
2795
- const params = [now()];
2796
- if (input.name !== undefined) {
2797
- sets.push("name = ?");
2798
- params.push(input.name);
2799
- }
2800
- if (input.description !== undefined) {
2801
- sets.push("description = ?");
2802
- params.push(input.description);
2803
- }
2804
- if (input.task_list_id !== undefined) {
2805
- sets.push("task_list_id = ?");
2806
- params.push(input.task_list_id);
2807
- }
2808
- params.push(id);
2809
- d.run(`UPDATE projects SET ${sets.join(", ")} WHERE id = ?`, params);
2810
- return getProject(id, d);
2811
- }
2812
- function ensureProject(name, path, db) {
2813
- const d = db || getDatabase();
2814
- const existing = getProjectByPath(path, d);
2815
- if (existing)
2816
- return existing;
2817
- return createProject({ name, path }, d);
2818
- }
2819
- var init_projects = __esm(() => {
2820
- init_types();
2821
- init_database();
2872
+ init_projects();
2822
2873
  });
2823
2874
 
2824
2875
  // src/db/agents.ts
@@ -9565,9 +9616,11 @@ function formatTaskLine(t) {
9565
9616
  return `${chalk.dim(t.id.slice(0, 8))} ${statusFn(t.status.padEnd(11))} ${priorityFn(t.priority.padEnd(8))} ${t.title}${assigned}${lock}${tags}${plan}`;
9566
9617
  }
9567
9618
  program2.name("todos").description("Universal task management for AI coding agents").version(getPackageVersion()).option("--project <path>", "Project path").option("--json", "Output as JSON").option("--agent <name>", "Agent name").option("--session <id>", "Session ID");
9568
- program2.command("add <title>").description("Create a new task").option("-d, --description <text>", "Task description").option("-p, --priority <level>", "Priority: low, medium, high, critical").option("--parent <id>", "Parent task ID").option("--tags <tags>", "Comma-separated tags").option("--plan <id>", "Assign to a plan").option("--assign <agent>", "Assign to agent").option("--status <status>", "Initial status").option("--list <id>", "Task list ID").action((title, opts) => {
9619
+ program2.command("add <title>").description("Create a new task").option("-d, --description <text>", "Task description").option("-p, --priority <level>", "Priority: low, medium, high, critical").option("--parent <id>", "Parent task ID").option("-t, --tags <tags>", "Comma-separated tags").option("--tag <tags>", "Comma-separated tags (alias for --tags)").option("--plan <id>", "Assign to a plan").option("--assign <agent>", "Assign to agent").option("--status <status>", "Initial status").option("--list <id>", "Task list ID").option("--task-list <id>", "Task list ID (alias for --list)").action((title, opts) => {
9569
9620
  const globalOpts = program2.opts();
9570
9621
  const projectId = autoProject(globalOpts);
9622
+ opts.tags = opts.tags || opts.tag;
9623
+ opts.list = opts.list || opts.taskList;
9571
9624
  const taskListId = opts.list ? (() => {
9572
9625
  const db = getDatabase();
9573
9626
  const id = resolvePartialId(db, "task_lists", opts.list);
@@ -9607,8 +9660,10 @@ program2.command("add <title>").description("Create a new task").option("-d, --d
9607
9660
  console.log(formatTaskLine(task));
9608
9661
  }
9609
9662
  });
9610
- program2.command("list").description("List tasks").option("-s, --status <status>", "Filter by status").option("-p, --priority <priority>", "Filter by priority").option("--assigned <agent>", "Filter by assigned agent").option("--tags <tags>", "Filter by tags (comma-separated)").option("-a, --all", "Show all tasks (including completed/cancelled)").option("--list <id>", "Filter by task list ID").action((opts) => {
9663
+ program2.command("list").description("List tasks").option("-s, --status <status>", "Filter by status").option("-p, --priority <priority>", "Filter by priority").option("--assigned <agent>", "Filter by assigned agent").option("--tags <tags>", "Filter by tags (comma-separated)").option("--tag <tags>", "Filter by tags (alias for --tags)").option("-a, --all", "Show all tasks (including completed/cancelled)").option("--list <id>", "Filter by task list ID").option("--task-list <id>", "Filter by task list ID (alias for --list)").action((opts) => {
9611
9664
  const globalOpts = program2.opts();
9665
+ opts.tags = opts.tags || opts.tag;
9666
+ opts.list = opts.list || opts.taskList;
9612
9667
  const projectId = autoProject(globalOpts);
9613
9668
  const filter = {};
9614
9669
  if (projectId)
@@ -9720,8 +9775,10 @@ program2.command("show <id>").description("Show full task details").action((id)
9720
9775
  }
9721
9776
  }
9722
9777
  });
9723
- program2.command("update <id>").description("Update a task").option("--title <text>", "New title").option("-d, --description <text>", "New description").option("-s, --status <status>", "New status").option("-p, --priority <priority>", "New priority").option("--assign <agent>", "Assign to agent").option("--tags <tags>", "New tags (comma-separated)").option("--list <id>", "Move to a task list").action((id, opts) => {
9778
+ program2.command("update <id>").description("Update a task").option("--title <text>", "New title").option("-d, --description <text>", "New description").option("-s, --status <status>", "New status").option("-p, --priority <priority>", "New priority").option("--assign <agent>", "Assign to agent").option("--tags <tags>", "New tags (comma-separated)").option("--tag <tags>", "New tags (alias for --tags)").option("--list <id>", "Move to a task list").option("--task-list <id>", "Move to a task list (alias for --list)").action((id, opts) => {
9724
9779
  const globalOpts = program2.opts();
9780
+ opts.tags = opts.tags || opts.tag;
9781
+ opts.list = opts.list || opts.taskList;
9725
9782
  const resolvedId = resolveTaskId(id);
9726
9783
  const current = getTask(resolvedId);
9727
9784
  if (!current) {
package/dist/index.js CHANGED
@@ -194,6 +194,15 @@ var MIGRATIONS = [
194
194
  CREATE INDEX IF NOT EXISTS idx_tasks_task_list ON tasks(task_list_id);
195
195
 
196
196
  INSERT OR IGNORE INTO _migrations (id) VALUES (5);
197
+ `,
198
+ `
199
+ ALTER TABLE projects ADD COLUMN task_prefix TEXT;
200
+ ALTER TABLE projects ADD COLUMN task_counter INTEGER NOT NULL DEFAULT 0;
201
+
202
+ ALTER TABLE tasks ADD COLUMN short_id TEXT;
203
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_tasks_short_id ON tasks(short_id) WHERE short_id IS NOT NULL;
204
+
205
+ INSERT OR IGNORE INTO _migrations (id) VALUES (6);
197
206
  `
198
207
  ];
199
208
  var _db = null;
@@ -231,6 +240,13 @@ function ensureTableMigrations(db) {
231
240
  db.exec(MIGRATIONS[4]);
232
241
  }
233
242
  } catch {}
243
+ try {
244
+ db.query("SELECT task_prefix FROM projects LIMIT 0").get();
245
+ } catch {
246
+ try {
247
+ db.exec(MIGRATIONS[5]);
248
+ } catch {}
249
+ }
234
250
  }
235
251
  function backfillTaskTags(db) {
236
252
  try {
@@ -401,6 +417,100 @@ class DependencyCycleError extends Error {
401
417
  }
402
418
  }
403
419
 
420
+ // src/db/projects.ts
421
+ function slugify(name) {
422
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
423
+ }
424
+ function generatePrefix(name, db) {
425
+ const words = name.replace(/[^a-zA-Z0-9\s]/g, "").trim().split(/\s+/);
426
+ let prefix;
427
+ if (words.length >= 3) {
428
+ prefix = words.slice(0, 3).map((w) => w[0].toUpperCase()).join("");
429
+ } else if (words.length === 2) {
430
+ prefix = (words[0].slice(0, 2) + words[1][0]).toUpperCase();
431
+ } else {
432
+ prefix = words[0].slice(0, 3).toUpperCase();
433
+ }
434
+ let candidate = prefix;
435
+ let suffix = 1;
436
+ while (true) {
437
+ const existing = db.query("SELECT id FROM projects WHERE task_prefix = ?").get(candidate);
438
+ if (!existing)
439
+ return candidate;
440
+ suffix++;
441
+ candidate = `${prefix}${suffix}`;
442
+ }
443
+ }
444
+ function createProject(input, db) {
445
+ const d = db || getDatabase();
446
+ const id = uuid();
447
+ const timestamp = now();
448
+ const taskListId = input.task_list_id ?? `todos-${slugify(input.name)}`;
449
+ const taskPrefix = input.task_prefix || generatePrefix(input.name, d);
450
+ d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, created_at, updated_at)
451
+ VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix, timestamp, timestamp]);
452
+ return getProject(id, d);
453
+ }
454
+ function getProject(id, db) {
455
+ const d = db || getDatabase();
456
+ const row = d.query("SELECT * FROM projects WHERE id = ?").get(id);
457
+ return row;
458
+ }
459
+ function getProjectByPath(path, db) {
460
+ const d = db || getDatabase();
461
+ const row = d.query("SELECT * FROM projects WHERE path = ?").get(path);
462
+ return row;
463
+ }
464
+ function listProjects(db) {
465
+ const d = db || getDatabase();
466
+ return d.query("SELECT * FROM projects ORDER BY name").all();
467
+ }
468
+ function updateProject(id, input, db) {
469
+ const d = db || getDatabase();
470
+ const project = getProject(id, d);
471
+ if (!project)
472
+ throw new ProjectNotFoundError(id);
473
+ const sets = ["updated_at = ?"];
474
+ const params = [now()];
475
+ if (input.name !== undefined) {
476
+ sets.push("name = ?");
477
+ params.push(input.name);
478
+ }
479
+ if (input.description !== undefined) {
480
+ sets.push("description = ?");
481
+ params.push(input.description);
482
+ }
483
+ if (input.task_list_id !== undefined) {
484
+ sets.push("task_list_id = ?");
485
+ params.push(input.task_list_id);
486
+ }
487
+ params.push(id);
488
+ d.run(`UPDATE projects SET ${sets.join(", ")} WHERE id = ?`, params);
489
+ return getProject(id, d);
490
+ }
491
+ function deleteProject(id, db) {
492
+ const d = db || getDatabase();
493
+ const result = d.run("DELETE FROM projects WHERE id = ?", [id]);
494
+ return result.changes > 0;
495
+ }
496
+ function nextTaskShortId(projectId, db) {
497
+ const d = db || getDatabase();
498
+ const project = getProject(projectId, d);
499
+ if (!project || !project.task_prefix)
500
+ return null;
501
+ d.run("UPDATE projects SET task_counter = task_counter + 1, updated_at = ? WHERE id = ?", [now(), projectId]);
502
+ const updated = getProject(projectId, d);
503
+ const padded = String(updated.task_counter).padStart(5, "0");
504
+ return `${updated.task_prefix}-${padded}`;
505
+ }
506
+ function ensureProject(name, path, db) {
507
+ const d = db || getDatabase();
508
+ const existing = getProjectByPath(path, d);
509
+ if (existing)
510
+ return existing;
511
+ return createProject({ name, path }, d);
512
+ }
513
+
404
514
  // src/db/tasks.ts
405
515
  function rowToTask(row) {
406
516
  return {
@@ -429,14 +539,17 @@ function createTask(input, db) {
429
539
  const id = uuid();
430
540
  const timestamp = now();
431
541
  const tags = input.tags || [];
432
- d.run(`INSERT INTO tasks (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)
433
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?)`, [
542
+ const shortId = input.project_id ? nextTaskShortId(input.project_id, d) : null;
543
+ const title = shortId ? `${shortId}: ${input.title}` : input.title;
544
+ 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)
545
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?)`, [
434
546
  id,
547
+ shortId,
435
548
  input.project_id || null,
436
549
  input.parent_id || null,
437
550
  input.plan_id || null,
438
551
  input.task_list_id || null,
439
- input.title,
552
+ title,
440
553
  input.description || null,
441
554
  input.status || "pending",
442
555
  input.priority || "medium",
@@ -732,68 +845,6 @@ function wouldCreateCycle(taskId, dependsOn, db) {
732
845
  }
733
846
  return false;
734
847
  }
735
- // src/db/projects.ts
736
- function slugify(name) {
737
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
738
- }
739
- function createProject(input, db) {
740
- const d = db || getDatabase();
741
- const id = uuid();
742
- const timestamp = now();
743
- const taskListId = input.task_list_id ?? `todos-${slugify(input.name)}`;
744
- d.run(`INSERT INTO projects (id, name, path, description, task_list_id, created_at, updated_at)
745
- VALUES (?, ?, ?, ?, ?, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, timestamp, timestamp]);
746
- return getProject(id, d);
747
- }
748
- function getProject(id, db) {
749
- const d = db || getDatabase();
750
- const row = d.query("SELECT * FROM projects WHERE id = ?").get(id);
751
- return row;
752
- }
753
- function getProjectByPath(path, db) {
754
- const d = db || getDatabase();
755
- const row = d.query("SELECT * FROM projects WHERE path = ?").get(path);
756
- return row;
757
- }
758
- function listProjects(db) {
759
- const d = db || getDatabase();
760
- return d.query("SELECT * FROM projects ORDER BY name").all();
761
- }
762
- function updateProject(id, input, db) {
763
- const d = db || getDatabase();
764
- const project = getProject(id, d);
765
- if (!project)
766
- throw new ProjectNotFoundError(id);
767
- const sets = ["updated_at = ?"];
768
- const params = [now()];
769
- if (input.name !== undefined) {
770
- sets.push("name = ?");
771
- params.push(input.name);
772
- }
773
- if (input.description !== undefined) {
774
- sets.push("description = ?");
775
- params.push(input.description);
776
- }
777
- if (input.task_list_id !== undefined) {
778
- sets.push("task_list_id = ?");
779
- params.push(input.task_list_id);
780
- }
781
- params.push(id);
782
- d.run(`UPDATE projects SET ${sets.join(", ")} WHERE id = ?`, params);
783
- return getProject(id, d);
784
- }
785
- function deleteProject(id, db) {
786
- const d = db || getDatabase();
787
- const result = d.run("DELETE FROM projects WHERE id = ?", [id]);
788
- return result.changes > 0;
789
- }
790
- function ensureProject(name, path, db) {
791
- const d = db || getDatabase();
792
- const existing = getProjectByPath(path, d);
793
- if (existing)
794
- return existing;
795
- return createProject({ name, path }, d);
796
- }
797
848
  // src/db/plans.ts
798
849
  function createPlan(input, db) {
799
850
  const d = db || getDatabase();
@@ -1711,6 +1762,7 @@ export {
1711
1762
  removeDependency,
1712
1763
  registerAgent,
1713
1764
  now,
1765
+ nextTaskShortId,
1714
1766
  lockTask,
1715
1767
  loadConfig,
1716
1768
  listTasks,
package/dist/mcp/index.js CHANGED
@@ -4244,6 +4244,15 @@ var MIGRATIONS = [
4244
4244
  CREATE INDEX IF NOT EXISTS idx_tasks_task_list ON tasks(task_list_id);
4245
4245
 
4246
4246
  INSERT OR IGNORE INTO _migrations (id) VALUES (5);
4247
+ `,
4248
+ `
4249
+ ALTER TABLE projects ADD COLUMN task_prefix TEXT;
4250
+ ALTER TABLE projects ADD COLUMN task_counter INTEGER NOT NULL DEFAULT 0;
4251
+
4252
+ ALTER TABLE tasks ADD COLUMN short_id TEXT;
4253
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_tasks_short_id ON tasks(short_id) WHERE short_id IS NOT NULL;
4254
+
4255
+ INSERT OR IGNORE INTO _migrations (id) VALUES (6);
4247
4256
  `
4248
4257
  ];
4249
4258
  var _db = null;
@@ -4281,6 +4290,13 @@ function ensureTableMigrations(db) {
4281
4290
  db.exec(MIGRATIONS[4]);
4282
4291
  }
4283
4292
  } catch {}
4293
+ try {
4294
+ db.query("SELECT task_prefix FROM projects LIMIT 0").get();
4295
+ } catch {
4296
+ try {
4297
+ db.exec(MIGRATIONS[5]);
4298
+ } catch {}
4299
+ }
4284
4300
  }
4285
4301
  function backfillTaskTags(db) {
4286
4302
  try {
@@ -4347,6 +4363,60 @@ function resolvePartialId(db, table, partialId) {
4347
4363
  return null;
4348
4364
  }
4349
4365
 
4366
+ // src/db/projects.ts
4367
+ function slugify(name) {
4368
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
4369
+ }
4370
+ function generatePrefix(name, db) {
4371
+ const words = name.replace(/[^a-zA-Z0-9\s]/g, "").trim().split(/\s+/);
4372
+ let prefix;
4373
+ if (words.length >= 3) {
4374
+ prefix = words.slice(0, 3).map((w) => w[0].toUpperCase()).join("");
4375
+ } else if (words.length === 2) {
4376
+ prefix = (words[0].slice(0, 2) + words[1][0]).toUpperCase();
4377
+ } else {
4378
+ prefix = words[0].slice(0, 3).toUpperCase();
4379
+ }
4380
+ let candidate = prefix;
4381
+ let suffix = 1;
4382
+ while (true) {
4383
+ const existing = db.query("SELECT id FROM projects WHERE task_prefix = ?").get(candidate);
4384
+ if (!existing)
4385
+ return candidate;
4386
+ suffix++;
4387
+ candidate = `${prefix}${suffix}`;
4388
+ }
4389
+ }
4390
+ function createProject(input, db) {
4391
+ const d = db || getDatabase();
4392
+ const id = uuid();
4393
+ const timestamp = now();
4394
+ const taskListId = input.task_list_id ?? `todos-${slugify(input.name)}`;
4395
+ const taskPrefix = input.task_prefix || generatePrefix(input.name, d);
4396
+ d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, created_at, updated_at)
4397
+ VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix, timestamp, timestamp]);
4398
+ return getProject(id, d);
4399
+ }
4400
+ function getProject(id, db) {
4401
+ const d = db || getDatabase();
4402
+ const row = d.query("SELECT * FROM projects WHERE id = ?").get(id);
4403
+ return row;
4404
+ }
4405
+ function listProjects(db) {
4406
+ const d = db || getDatabase();
4407
+ return d.query("SELECT * FROM projects ORDER BY name").all();
4408
+ }
4409
+ function nextTaskShortId(projectId, db) {
4410
+ const d = db || getDatabase();
4411
+ const project = getProject(projectId, d);
4412
+ if (!project || !project.task_prefix)
4413
+ return null;
4414
+ d.run("UPDATE projects SET task_counter = task_counter + 1, updated_at = ? WHERE id = ?", [now(), projectId]);
4415
+ const updated = getProject(projectId, d);
4416
+ const padded = String(updated.task_counter).padStart(5, "0");
4417
+ return `${updated.task_prefix}-${padded}`;
4418
+ }
4419
+
4350
4420
  // src/db/tasks.ts
4351
4421
  function rowToTask(row) {
4352
4422
  return {
@@ -4375,14 +4445,17 @@ function createTask(input, db) {
4375
4445
  const id = uuid();
4376
4446
  const timestamp = now();
4377
4447
  const tags = input.tags || [];
4378
- d.run(`INSERT INTO tasks (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)
4379
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?)`, [
4448
+ const shortId = input.project_id ? nextTaskShortId(input.project_id, d) : null;
4449
+ const title = shortId ? `${shortId}: ${input.title}` : input.title;
4450
+ 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)
4451
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?)`, [
4380
4452
  id,
4453
+ shortId,
4381
4454
  input.project_id || null,
4382
4455
  input.parent_id || null,
4383
4456
  input.plan_id || null,
4384
4457
  input.task_list_id || null,
4385
- input.title,
4458
+ title,
4386
4459
  input.description || null,
4387
4460
  input.status || "pending",
4388
4461
  input.priority || "medium",
@@ -4695,29 +4768,6 @@ function getComment(id, db) {
4695
4768
  return d.query("SELECT * FROM task_comments WHERE id = ?").get(id);
4696
4769
  }
4697
4770
 
4698
- // src/db/projects.ts
4699
- function slugify(name) {
4700
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
4701
- }
4702
- function createProject(input, db) {
4703
- const d = db || getDatabase();
4704
- const id = uuid();
4705
- const timestamp = now();
4706
- const taskListId = input.task_list_id ?? `todos-${slugify(input.name)}`;
4707
- d.run(`INSERT INTO projects (id, name, path, description, task_list_id, created_at, updated_at)
4708
- VALUES (?, ?, ?, ?, ?, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, timestamp, timestamp]);
4709
- return getProject(id, d);
4710
- }
4711
- function getProject(id, db) {
4712
- const d = db || getDatabase();
4713
- const row = d.query("SELECT * FROM projects WHERE id = ?").get(id);
4714
- return row;
4715
- }
4716
- function listProjects(db) {
4717
- const d = db || getDatabase();
4718
- return d.query("SELECT * FROM projects ORDER BY name").all();
4719
- }
4720
-
4721
4771
  // src/db/plans.ts
4722
4772
  function createPlan(input, db) {
4723
4773
  const d = db || getDatabase();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/todos",
3
- "version": "0.9.2",
3
+ "version": "0.9.4",
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",