@hasna/todos 0.9.3 → 0.9.5

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,105 @@ 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
+ if (!existing.task_prefix) {
2536
+ const prefix = generatePrefix(existing.name, d);
2537
+ d.run("UPDATE projects SET task_prefix = ?, updated_at = ? WHERE id = ?", [prefix, now(), existing.id]);
2538
+ return getProject(existing.id, d);
2539
+ }
2540
+ return existing;
2541
+ }
2542
+ return createProject({ name, path }, d);
2543
+ }
2544
+ var init_projects = __esm(() => {
2545
+ init_types();
2546
+ init_database();
2547
+ });
2548
+
2434
2549
  // src/db/tasks.ts
2435
2550
  function rowToTask(row) {
2436
2551
  return {
@@ -2459,14 +2574,17 @@ function createTask(input, db) {
2459
2574
  const id = uuid();
2460
2575
  const timestamp = now();
2461
2576
  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, ?, ?)`, [
2577
+ const shortId = input.project_id ? nextTaskShortId(input.project_id, d) : null;
2578
+ const title = shortId ? `${shortId}: ${input.title}` : input.title;
2579
+ 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)
2580
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?)`, [
2464
2581
  id,
2582
+ shortId,
2465
2583
  input.project_id || null,
2466
2584
  input.parent_id || null,
2467
2585
  input.plan_id || null,
2468
2586
  input.task_list_id || null,
2469
- input.title,
2587
+ title,
2470
2588
  input.description || null,
2471
2589
  input.status || "pending",
2472
2590
  input.priority || "medium",
@@ -2757,68 +2875,7 @@ function wouldCreateCycle(taskId, dependsOn, db) {
2757
2875
  var init_tasks = __esm(() => {
2758
2876
  init_types();
2759
2877
  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();
2878
+ init_projects();
2822
2879
  });
2823
2880
 
2824
2881
  // src/db/agents.ts
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,106 @@ 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
+ if (!existing.task_prefix) {
511
+ const prefix = generatePrefix(existing.name, d);
512
+ d.run("UPDATE projects SET task_prefix = ?, updated_at = ? WHERE id = ?", [prefix, now(), existing.id]);
513
+ return getProject(existing.id, d);
514
+ }
515
+ return existing;
516
+ }
517
+ return createProject({ name, path }, d);
518
+ }
519
+
404
520
  // src/db/tasks.ts
405
521
  function rowToTask(row) {
406
522
  return {
@@ -429,14 +545,17 @@ function createTask(input, db) {
429
545
  const id = uuid();
430
546
  const timestamp = now();
431
547
  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, ?, ?)`, [
548
+ const shortId = input.project_id ? nextTaskShortId(input.project_id, d) : null;
549
+ const title = shortId ? `${shortId}: ${input.title}` : input.title;
550
+ 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)
551
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?)`, [
434
552
  id,
553
+ shortId,
435
554
  input.project_id || null,
436
555
  input.parent_id || null,
437
556
  input.plan_id || null,
438
557
  input.task_list_id || null,
439
- input.title,
558
+ title,
440
559
  input.description || null,
441
560
  input.status || "pending",
442
561
  input.priority || "medium",
@@ -732,68 +851,6 @@ function wouldCreateCycle(taskId, dependsOn, db) {
732
851
  }
733
852
  return false;
734
853
  }
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
854
  // src/db/plans.ts
798
855
  function createPlan(input, db) {
799
856
  const d = db || getDatabase();
@@ -1711,6 +1768,7 @@ export {
1711
1768
  removeDependency,
1712
1769
  registerAgent,
1713
1770
  now,
1771
+ nextTaskShortId,
1714
1772
  lockTask,
1715
1773
  loadConfig,
1716
1774
  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.3",
3
+ "version": "0.9.5",
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",