@hasna/todos 0.11.88 → 0.11.89

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 (44) hide show
  1. package/dist/cli/cloud-router.d.ts +10 -2
  2. package/dist/cli/cloud-router.d.ts.map +1 -1
  3. package/dist/cli/commands/agent-commands.d.ts.map +1 -1
  4. package/dist/cli/commands/project-commands.d.ts.map +1 -1
  5. package/dist/cli/commands/task-commands.d.ts.map +1 -1
  6. package/dist/cli/index.js +950 -118
  7. package/dist/contracts.js +301 -72
  8. package/dist/db/projects.d.ts +2 -2
  9. package/dist/db/projects.d.ts.map +1 -1
  10. package/dist/db/schema.d.ts.map +1 -1
  11. package/dist/db/slug-claims.d.ts +11 -0
  12. package/dist/db/slug-claims.d.ts.map +1 -0
  13. package/dist/db/task-lists.d.ts.map +1 -1
  14. package/dist/index.js +641 -100
  15. package/dist/lib/slugs.d.ts +19 -0
  16. package/dist/lib/slugs.d.ts.map +1 -0
  17. package/dist/mcp/index.js +897 -102
  18. package/dist/registry.js +301 -72
  19. package/dist/release-provenance.json +3 -3
  20. package/dist/sdk/index.js +56 -0
  21. package/dist/sdk/v1.generated.d.ts +79 -0
  22. package/dist/sdk/v1.generated.d.ts.map +1 -1
  23. package/dist/server/cloud.d.ts +2 -0
  24. package/dist/server/cloud.d.ts.map +1 -1
  25. package/dist/server/index.js +906 -108
  26. package/dist/server/openapi.d.ts +464 -0
  27. package/dist/server/openapi.d.ts.map +1 -1
  28. package/dist/server/v1.d.ts.map +1 -1
  29. package/dist/storage/index.d.ts +2 -2
  30. package/dist/storage/index.d.ts.map +1 -1
  31. package/dist/storage/interfaces.d.ts +3 -2
  32. package/dist/storage/interfaces.d.ts.map +1 -1
  33. package/dist/storage/local-sqlite.d.ts.map +1 -1
  34. package/dist/storage/postgres-adapter.d.ts.map +1 -1
  35. package/dist/storage/postgres-sync.d.ts +30 -0
  36. package/dist/storage/postgres-sync.d.ts.map +1 -1
  37. package/dist/storage/shadow.d.ts.map +1 -1
  38. package/dist/storage/sqlite-snapshot.d.ts.map +1 -1
  39. package/dist/storage.d.ts +2 -2
  40. package/dist/storage.d.ts.map +1 -1
  41. package/dist/storage.js +635 -88
  42. package/dist/types/index.d.ts +19 -0
  43. package/dist/types/index.d.ts.map +1 -1
  44. package/package.json +1 -1
package/dist/contracts.js CHANGED
@@ -1748,6 +1748,95 @@ function ensureSchema(db) {
1748
1748
  )`);
1749
1749
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_machine_paths_project ON project_machine_paths(project_id)");
1750
1750
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_machine_paths_machine ON project_machine_paths(machine_id)");
1751
+ ensureTable("canonical_slug_claims", `
1752
+ CREATE TABLE canonical_slug_claims (
1753
+ kind TEXT NOT NULL CHECK(kind IN ('project', 'task_list')),
1754
+ scope_key TEXT NOT NULL,
1755
+ slug TEXT NOT NULL,
1756
+ object_id TEXT NOT NULL,
1757
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1758
+ PRIMARY KEY (kind, scope_key, slug)
1759
+ )`);
1760
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_canonical_slug_claims_object ON canonical_slug_claims(kind, object_id)");
1761
+ ensureColumn("projects", "task_list_id", "TEXT");
1762
+ db.exec(`CREATE TRIGGER IF NOT EXISTS claim_project_canonical_slug_insert
1763
+ BEFORE INSERT ON projects
1764
+ WHEN NEW.task_list_id IS NOT NULL AND NEW.task_list_id <> ''
1765
+ BEGIN
1766
+ SELECT CASE WHEN EXISTS (
1767
+ SELECT 1 FROM projects WHERE id <> NEW.id AND task_list_id = NEW.task_list_id
1768
+ ) THEN RAISE(ABORT, 'PROJECT_SLUG_CONFLICT') END;
1769
+ INSERT OR IGNORE INTO canonical_slug_claims(kind, scope_key, slug, object_id)
1770
+ VALUES ('project', 'global', NEW.task_list_id, NEW.id);
1771
+ SELECT CASE WHEN (
1772
+ SELECT object_id FROM canonical_slug_claims
1773
+ WHERE kind = 'project' AND scope_key = 'global' AND slug = NEW.task_list_id
1774
+ ) <> NEW.id THEN RAISE(ABORT, 'PROJECT_SLUG_CONFLICT') END;
1775
+ END`);
1776
+ db.exec(`CREATE TRIGGER IF NOT EXISTS claim_project_canonical_slug_update
1777
+ BEFORE UPDATE OF task_list_id ON projects
1778
+ WHEN NEW.task_list_id IS NOT OLD.task_list_id
1779
+ BEGIN
1780
+ DELETE FROM canonical_slug_claims WHERE kind = 'project' AND object_id = NEW.id;
1781
+ SELECT CASE WHEN EXISTS (
1782
+ SELECT 1 FROM projects WHERE id <> NEW.id AND task_list_id = NEW.task_list_id
1783
+ ) AND NEW.task_list_id IS NOT NULL AND NEW.task_list_id <> ''
1784
+ THEN RAISE(ABORT, 'PROJECT_SLUG_CONFLICT') END;
1785
+ INSERT OR IGNORE INTO canonical_slug_claims(kind, scope_key, slug, object_id)
1786
+ SELECT 'project', 'global', NEW.task_list_id, NEW.id
1787
+ WHERE NEW.task_list_id IS NOT NULL AND NEW.task_list_id <> '';
1788
+ SELECT CASE WHEN NEW.task_list_id IS NOT NULL AND NEW.task_list_id <> '' AND (
1789
+ SELECT object_id FROM canonical_slug_claims
1790
+ WHERE kind = 'project' AND scope_key = 'global' AND slug = NEW.task_list_id
1791
+ ) <> NEW.id THEN RAISE(ABORT, 'PROJECT_SLUG_CONFLICT') END;
1792
+ END`);
1793
+ db.exec(`CREATE TRIGGER IF NOT EXISTS release_project_canonical_slug_delete
1794
+ AFTER DELETE ON projects
1795
+ BEGIN
1796
+ DELETE FROM canonical_slug_claims WHERE kind = 'project' AND object_id = OLD.id;
1797
+ END`);
1798
+ db.exec(`CREATE TRIGGER IF NOT EXISTS claim_task_list_canonical_slug_insert
1799
+ BEFORE INSERT ON task_lists
1800
+ WHEN NEW.slug IS NOT NULL AND NEW.slug <> ''
1801
+ BEGIN
1802
+ SELECT CASE WHEN EXISTS (
1803
+ SELECT 1 FROM task_lists
1804
+ WHERE id <> NEW.id AND project_id IS NEW.project_id AND slug = NEW.slug
1805
+ ) THEN RAISE(ABORT, 'TASK_LIST_SLUG_CONFLICT') END;
1806
+ INSERT OR IGNORE INTO canonical_slug_claims(kind, scope_key, slug, object_id)
1807
+ VALUES ('task_list', CASE WHEN NEW.project_id IS NULL THEN 'standalone:' ELSE 'project:' || NEW.project_id END, NEW.slug, NEW.id);
1808
+ SELECT CASE WHEN (
1809
+ SELECT object_id FROM canonical_slug_claims
1810
+ WHERE kind = 'task_list'
1811
+ AND scope_key = CASE WHEN NEW.project_id IS NULL THEN 'standalone:' ELSE 'project:' || NEW.project_id END
1812
+ AND slug = NEW.slug
1813
+ ) <> NEW.id THEN RAISE(ABORT, 'TASK_LIST_SLUG_CONFLICT') END;
1814
+ END`);
1815
+ db.exec(`CREATE TRIGGER IF NOT EXISTS claim_task_list_canonical_slug_update
1816
+ BEFORE UPDATE OF slug, project_id ON task_lists
1817
+ WHEN NEW.slug IS NOT OLD.slug OR NEW.project_id IS NOT OLD.project_id
1818
+ BEGIN
1819
+ DELETE FROM canonical_slug_claims WHERE kind = 'task_list' AND object_id = NEW.id;
1820
+ SELECT CASE WHEN EXISTS (
1821
+ SELECT 1 FROM task_lists
1822
+ WHERE id <> NEW.id AND project_id IS NEW.project_id AND slug = NEW.slug
1823
+ ) AND NEW.slug IS NOT NULL AND NEW.slug <> ''
1824
+ THEN RAISE(ABORT, 'TASK_LIST_SLUG_CONFLICT') END;
1825
+ INSERT OR IGNORE INTO canonical_slug_claims(kind, scope_key, slug, object_id)
1826
+ SELECT 'task_list', CASE WHEN NEW.project_id IS NULL THEN 'standalone:' ELSE 'project:' || NEW.project_id END, NEW.slug, NEW.id
1827
+ WHERE NEW.slug IS NOT NULL AND NEW.slug <> '';
1828
+ SELECT CASE WHEN NEW.slug IS NOT NULL AND NEW.slug <> '' AND (
1829
+ SELECT object_id FROM canonical_slug_claims
1830
+ WHERE kind = 'task_list'
1831
+ AND scope_key = CASE WHEN NEW.project_id IS NULL THEN 'standalone:' ELSE 'project:' || NEW.project_id END
1832
+ AND slug = NEW.slug
1833
+ ) <> NEW.id THEN RAISE(ABORT, 'TASK_LIST_SLUG_CONFLICT') END;
1834
+ END`);
1835
+ db.exec(`CREATE TRIGGER IF NOT EXISTS release_task_list_canonical_slug_delete
1836
+ AFTER DELETE ON task_lists
1837
+ BEGIN
1838
+ DELETE FROM canonical_slug_claims WHERE kind = 'task_list' AND object_id = OLD.id;
1839
+ END`);
1751
1840
  ensureTable("storage_tombstones", `
1752
1841
  CREATE TABLE storage_tombstones (
1753
1842
  id TEXT PRIMARY KEY,
@@ -3141,7 +3230,7 @@ var init_database = __esm(() => {
3141
3230
  });
3142
3231
 
3143
3232
  // src/types/index.ts
3144
- var TASK_STATUSES, TASK_PRIORITIES, PLAN_STATUSES, VersionConflictError, TaskNotFoundError, ProjectNotFoundError, PlanNotFoundError, LockError, AgentNotFoundError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError, DISPATCH_STATUSES, DispatchNotFoundError;
3233
+ var TASK_STATUSES, TASK_PRIORITIES, PLAN_STATUSES, VersionConflictError, TaskNotFoundError, ProjectNotFoundError, ResourceConflictError, PlanNotFoundError, LockError, AgentNotFoundError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError, DISPATCH_STATUSES, DispatchNotFoundError;
3145
3234
  var init_types = __esm(() => {
3146
3235
  TASK_STATUSES = [
3147
3236
  "pending",
@@ -3191,6 +3280,14 @@ var init_types = __esm(() => {
3191
3280
  this.name = "ProjectNotFoundError";
3192
3281
  }
3193
3282
  };
3283
+ ResourceConflictError = class ResourceConflictError extends Error {
3284
+ code;
3285
+ constructor(code, message) {
3286
+ super(message);
3287
+ this.code = code;
3288
+ this.name = "ResourceConflictError";
3289
+ }
3290
+ };
3194
3291
  PlanNotFoundError = class PlanNotFoundError extends Error {
3195
3292
  planId;
3196
3293
  static code = "PLAN_NOT_FOUND";
@@ -3496,9 +3593,94 @@ var init_storage_tombstones = __esm(() => {
3496
3593
  init_machines();
3497
3594
  });
3498
3595
 
3596
+ // src/lib/slugs.ts
3597
+ function normalizeSlug(value) {
3598
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
3599
+ }
3600
+ function isCanonicalSlug(value) {
3601
+ return typeof value === "string" && value.length > 0 && normalizeSlug(value) === value;
3602
+ }
3603
+ function isValidTaskListProjectScope(value) {
3604
+ return value === undefined || value === null || typeof value === "string" && value.trim().length > 0;
3605
+ }
3606
+ function validateSnapshotRoutingRecords(projects, taskLists) {
3607
+ const errors = [];
3608
+ const projectSlugs = new Map;
3609
+ const taskListSlugs = new Map;
3610
+ for (const project of projects) {
3611
+ if (!isCanonicalSlug(project.task_list_id)) {
3612
+ errors.push(`project ${project.id}: task_list_id must be non-empty canonical kebab-case`);
3613
+ continue;
3614
+ }
3615
+ const existing = projectSlugs.get(project.task_list_id);
3616
+ if (projectSlugs.has(project.task_list_id)) {
3617
+ errors.push(`project ${project.id}: task_list_id duplicates project ${existing}: ${project.task_list_id}`);
3618
+ } else {
3619
+ projectSlugs.set(project.task_list_id, project.id);
3620
+ }
3621
+ }
3622
+ for (const taskList of taskLists) {
3623
+ if (!isCanonicalSlug(taskList.slug)) {
3624
+ errors.push(`task list ${taskList.id}: slug must be non-empty canonical kebab-case`);
3625
+ continue;
3626
+ }
3627
+ if (!isValidTaskListProjectScope(taskList.project_id)) {
3628
+ errors.push(`task list ${taskList.id}: project_id must be null, missing, or a non-empty string`);
3629
+ continue;
3630
+ }
3631
+ const scope = taskList.project_id ?? null;
3632
+ const scopedSlugs = taskListSlugs.get(scope) ?? new Map;
3633
+ const existing = scopedSlugs.get(taskList.slug);
3634
+ if (scopedSlugs.has(taskList.slug)) {
3635
+ errors.push(`task list ${taskList.id}: slug duplicates task list ${existing} in the same scope: ${taskList.slug}`);
3636
+ } else {
3637
+ scopedSlugs.set(taskList.slug, taskList.id);
3638
+ taskListSlugs.set(scope, scopedSlugs);
3639
+ }
3640
+ }
3641
+ return errors;
3642
+ }
3643
+ function validateSnapshotRoutingDestinationConflicts(projects, taskLists, existingProjects, existingTaskLists) {
3644
+ const errors = [];
3645
+ for (const project of projects) {
3646
+ const current = existingProjects.find((candidate) => candidate.id === project.id);
3647
+ if (current?.task_list_id === project.task_list_id)
3648
+ continue;
3649
+ const conflict = existingProjects.find((candidate) => candidate.id !== project.id && candidate.task_list_id === project.task_list_id);
3650
+ if (conflict) {
3651
+ errors.push(`project ${project.id}: task_list_id conflicts with existing project ${conflict.id}: ${String(project.task_list_id)}`);
3652
+ }
3653
+ }
3654
+ for (const taskList of taskLists) {
3655
+ const projectId = taskList.project_id ?? null;
3656
+ const current = existingTaskLists.find((candidate) => candidate.id === taskList.id);
3657
+ if ((current?.project_id ?? null) === projectId && current?.slug === taskList.slug)
3658
+ continue;
3659
+ const conflict = existingTaskLists.find((candidate) => candidate.id !== taskList.id && (candidate.project_id ?? null) === projectId && candidate.slug === taskList.slug);
3660
+ if (conflict) {
3661
+ errors.push(`task list ${taskList.id}: slug conflicts with existing task list ${conflict.id} in the same scope: ${String(taskList.slug)}`);
3662
+ }
3663
+ }
3664
+ return errors;
3665
+ }
3666
+
3667
+ // src/db/slug-claims.ts
3668
+ function taskListSlugScopeKey(projectId) {
3669
+ return projectId ? `project:${projectId}` : "standalone:";
3670
+ }
3671
+ function claimCanonicalSlug(kind, scopeKey, slug, objectId, db) {
3672
+ db.run(`INSERT OR IGNORE INTO canonical_slug_claims (kind, scope_key, slug, object_id)
3673
+ VALUES (?, ?, ?, ?)`, [kind, scopeKey, slug, objectId]);
3674
+ const claim = db.query("SELECT object_id FROM canonical_slug_claims WHERE kind = ? AND scope_key = ? AND slug = ?").get(kind, scopeKey, slug);
3675
+ return claim?.object_id === objectId;
3676
+ }
3677
+ function releaseCanonicalSlugClaims(kind, objectId, db) {
3678
+ db.run("DELETE FROM canonical_slug_claims WHERE kind = ? AND object_id = ?", [kind, objectId]);
3679
+ }
3680
+
3499
3681
  // src/db/projects.ts
3500
3682
  function slugify(name) {
3501
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
3683
+ return normalizeSlug(name);
3502
3684
  }
3503
3685
  function generatePrefix(name, db) {
3504
3686
  const words = name.replace(/[^a-zA-Z0-9\s]/g, "").trim().split(/\s+/);
@@ -3522,14 +3704,23 @@ function generatePrefix(name, db) {
3522
3704
  }
3523
3705
  function createProject(input, db) {
3524
3706
  const d = db || getDatabase();
3525
- const id = uuid();
3526
- const timestamp = now();
3527
- const taskListId = input.task_list_id ?? `todos-${slugify(input.name)}`;
3528
- const taskPrefix = input.task_prefix || generatePrefix(input.name, d);
3529
- const machineId = currentStorageMachineId(d);
3530
- d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, created_at, updated_at, machine_id)
3531
- VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix, timestamp, timestamp, machineId]);
3532
- return getProject(id, d);
3707
+ return d.transaction(() => {
3708
+ const id = uuid();
3709
+ const timestamp = now();
3710
+ const derivedSlug = slugify(input.name);
3711
+ const taskListId = input.task_list_id === undefined ? `todos-${derivedSlug}` : slugify(input.task_list_id);
3712
+ if (!derivedSlug || !taskListId)
3713
+ throw new Error("Project name and task-list slug must be non-empty");
3714
+ const slugConflict = d.query("SELECT id FROM projects WHERE task_list_id = ? LIMIT 1").get(taskListId);
3715
+ if (slugConflict || !claimCanonicalSlug("project", "global", taskListId, id, d)) {
3716
+ throw new ResourceConflictError("PROJECT_SLUG_CONFLICT", `Project slug "${taskListId}" already exists`);
3717
+ }
3718
+ const taskPrefix = input.task_prefix || generatePrefix(input.name, d);
3719
+ const machineId = currentStorageMachineId(d);
3720
+ d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, created_at, updated_at, machine_id)
3721
+ VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix, timestamp, timestamp, machineId]);
3722
+ return getProject(id, d);
3723
+ })();
3533
3724
  }
3534
3725
  function getProject(id, db) {
3535
3726
  const d = db || getDatabase();
@@ -3557,6 +3748,9 @@ function updateProject(id, input, db) {
3557
3748
  const project = getProject(id, d);
3558
3749
  if (!project)
3559
3750
  throw new ProjectNotFoundError(id);
3751
+ if ("task_list_id" in input) {
3752
+ throw new Error("task_list_id cannot be changed by updateProject; use renameProject for an atomic canonical rename");
3753
+ }
3560
3754
  const sets = ["updated_at = ?"];
3561
3755
  const params = [now()];
3562
3756
  if (input.name !== undefined) {
@@ -3567,10 +3761,6 @@ function updateProject(id, input, db) {
3567
3761
  sets.push("description = ?");
3568
3762
  params.push(input.description);
3569
3763
  }
3570
- if (input.task_list_id !== undefined) {
3571
- sets.push("task_list_id = ?");
3572
- params.push(input.task_list_id);
3573
- }
3574
3764
  if (input.path !== undefined) {
3575
3765
  sets.push("path = ?");
3576
3766
  params.push(input.path);
@@ -3581,29 +3771,41 @@ function updateProject(id, input, db) {
3581
3771
  }
3582
3772
  function renameProject(id, input, db) {
3583
3773
  const d = db || getDatabase();
3584
- const project = getProject(id, d);
3585
- if (!project)
3586
- throw new ProjectNotFoundError(id);
3587
- let taskListsUpdated = 0;
3588
- const ts = now();
3589
- if (input.new_slug !== undefined) {
3590
- const normalised = input.new_slug.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-|-$/g, "");
3591
- if (!normalised)
3592
- throw new Error("Invalid slug \u2014 must be non-empty kebab-case");
3593
- const conflict = d.query("SELECT id FROM projects WHERE task_list_id = ? AND id != ?").get(normalised, id);
3594
- if (conflict)
3595
- throw new Error(`Slug "${normalised}" is already used by another project`);
3596
- const oldSlug = project.task_list_id;
3597
- d.run("UPDATE projects SET task_list_id = ?, updated_at = ? WHERE id = ?", [normalised, ts, id]);
3598
- if (oldSlug) {
3599
- 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]);
3600
- taskListsUpdated = result.changes;
3774
+ return d.transaction(() => {
3775
+ const project = getProject(id, d);
3776
+ if (!project)
3777
+ throw new ProjectNotFoundError(id);
3778
+ let taskListsUpdated = 0;
3779
+ const ts = now();
3780
+ if (input.new_slug !== undefined) {
3781
+ const normalised = normalizeSlug(input.new_slug);
3782
+ if (!normalised)
3783
+ throw new Error("Invalid slug \u2014 must be non-empty kebab-case");
3784
+ const oldSlug = project.task_list_id;
3785
+ if (normalised !== oldSlug) {
3786
+ const conflict = d.query("SELECT id FROM projects WHERE task_list_id = ? AND id != ?").get(normalised, id);
3787
+ if (conflict) {
3788
+ throw new ResourceConflictError("PROJECT_SLUG_CONFLICT", `Slug "${normalised}" is already used by another project`);
3789
+ }
3790
+ const taskListConflict = d.query("SELECT id FROM task_lists WHERE project_id = ? AND slug = ? AND slug != COALESCE(?, '') LIMIT 1").get(id, normalised, oldSlug);
3791
+ if (taskListConflict) {
3792
+ throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task-list slug "${normalised}" is already used in project "${project.name}"`);
3793
+ }
3794
+ releaseCanonicalSlugClaims("project", id, d);
3795
+ if (!claimCanonicalSlug("project", "global", normalised, id, d)) {
3796
+ throw new ResourceConflictError("PROJECT_SLUG_CONFLICT", `Slug "${normalised}" is already used by another project`);
3797
+ }
3798
+ d.run("UPDATE projects SET task_list_id = ?, updated_at = ? WHERE id = ?", [normalised, ts, id]);
3799
+ }
3800
+ if (oldSlug && (normalised !== oldSlug || input.name !== undefined && input.name !== project.name)) {
3801
+ taskListsUpdated = d.query("UPDATE task_lists SET slug = ?, name = COALESCE(?, name), updated_at = ? WHERE project_id = ? AND slug = ? RETURNING id").all(normalised, input.name ?? null, ts, id, oldSlug).length;
3802
+ }
3601
3803
  }
3602
- }
3603
- if (input.name !== undefined) {
3604
- d.run("UPDATE projects SET name = ?, updated_at = ? WHERE id = ?", [input.name, ts, id]);
3605
- }
3606
- return { project: getProject(id, d), task_lists_updated: taskListsUpdated };
3804
+ if (input.name !== undefined && input.name !== project.name) {
3805
+ d.run("UPDATE projects SET name = ?, updated_at = ? WHERE id = ?", [input.name, ts, id]);
3806
+ }
3807
+ return { project: getProject(id, d), task_lists_updated: taskListsUpdated };
3808
+ })();
3607
3809
  }
3608
3810
  function deleteProject(id, db) {
3609
3811
  const d = db || getDatabase();
@@ -3615,8 +3817,10 @@ function deleteProject(id, db) {
3615
3817
  object_id: id,
3616
3818
  payload: project
3617
3819
  }, d);
3618
- const result = d.run("DELETE FROM projects WHERE id = ?", [id]);
3619
- return result.changes > 0;
3820
+ return d.transaction(() => {
3821
+ releaseCanonicalSlugClaims("project", id, d);
3822
+ return d.run("DELETE FROM projects WHERE id = ?", [id]).changes > 0;
3823
+ })();
3620
3824
  }
3621
3825
  function rowToSource(row) {
3622
3826
  return {
@@ -5175,19 +5379,22 @@ function rowToTaskList(row) {
5175
5379
  }
5176
5380
  function createTaskList(input, db) {
5177
5381
  const d = db || getDatabase();
5178
- const id = uuid();
5179
- const timestamp = now();
5180
- const slug = input.slug || slugify(input.name);
5181
- const machineId = currentStorageMachineId(d);
5182
- if (!input.project_id) {
5183
- const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
5184
- if (existing) {
5185
- throw new Error(`Standalone task list with slug "${slug}" already exists`);
5186
- }
5187
- }
5188
- d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at, machine_id)
5189
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp, machineId]);
5190
- return getTaskList(id, d);
5382
+ return d.transaction(() => {
5383
+ const id = uuid();
5384
+ const timestamp = now();
5385
+ const slug = normalizeSlug(input.slug === undefined ? input.name : input.slug);
5386
+ if (!slug)
5387
+ throw new Error("Invalid task-list slug \u2014 must be non-empty kebab-case");
5388
+ const machineId = currentStorageMachineId(d);
5389
+ const scopeKey = taskListSlugScopeKey(input.project_id);
5390
+ const existing = input.project_id ? d.query("SELECT id FROM task_lists WHERE project_id = ? AND slug = ?").get(input.project_id, slug) : d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
5391
+ if (existing || !claimCanonicalSlug("task_list", scopeKey, slug, id, d)) {
5392
+ throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${slug}" already exists in this scope`);
5393
+ }
5394
+ d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at, machine_id)
5395
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp, machineId]);
5396
+ return getTaskList(id, d);
5397
+ })();
5191
5398
  }
5192
5399
  function getTaskList(id, db) {
5193
5400
  const d = db || getDatabase();
@@ -5213,26 +5420,45 @@ function listTaskLists(projectId, db) {
5213
5420
  }
5214
5421
  function updateTaskList(id, input, db) {
5215
5422
  const d = db || getDatabase();
5216
- const existing = getTaskList(id, d);
5217
- if (!existing)
5218
- throw new TaskListNotFoundError(id);
5219
- const sets = ["updated_at = ?"];
5220
- const params = [now()];
5221
- if (input.name !== undefined) {
5222
- sets.push("name = ?");
5223
- params.push(input.name);
5224
- }
5225
- if (input.description !== undefined) {
5226
- sets.push("description = ?");
5227
- params.push(input.description);
5228
- }
5229
- if (input.metadata !== undefined) {
5230
- sets.push("metadata = ?");
5231
- params.push(JSON.stringify(input.metadata));
5232
- }
5233
- params.push(id);
5234
- d.run(`UPDATE task_lists SET ${sets.join(", ")} WHERE id = ?`, params);
5235
- return getTaskList(id, d);
5423
+ return d.transaction(() => {
5424
+ const existing = getTaskList(id, d);
5425
+ if (!existing)
5426
+ throw new TaskListNotFoundError(id);
5427
+ const sets = ["updated_at = ?"];
5428
+ const params = [now()];
5429
+ if (input.slug !== undefined) {
5430
+ const slug = slugify(input.slug);
5431
+ if (!slug)
5432
+ throw new Error("Invalid task-list slug \u2014 must be non-empty kebab-case");
5433
+ const duplicate = existing.project_id ? d.query("SELECT id FROM task_lists WHERE project_id = ? AND slug = ? AND id != ?").get(existing.project_id, slug, id) : d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ? AND id != ?").get(slug, id);
5434
+ if (duplicate) {
5435
+ throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${slug}" already exists in this scope`);
5436
+ }
5437
+ if (slug !== existing.slug) {
5438
+ releaseCanonicalSlugClaims("task_list", id, d);
5439
+ if (!claimCanonicalSlug("task_list", taskListSlugScopeKey(existing.project_id), slug, id, d)) {
5440
+ throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${slug}" already exists in this scope`);
5441
+ }
5442
+ }
5443
+ sets.push("slug = ?");
5444
+ params.push(slug);
5445
+ }
5446
+ if (input.name !== undefined) {
5447
+ sets.push("name = ?");
5448
+ params.push(input.name);
5449
+ }
5450
+ if (input.description !== undefined) {
5451
+ sets.push("description = ?");
5452
+ params.push(input.description);
5453
+ }
5454
+ if (input.metadata !== undefined) {
5455
+ sets.push("metadata = ?");
5456
+ params.push(JSON.stringify(input.metadata));
5457
+ }
5458
+ params.push(id);
5459
+ d.run(`UPDATE task_lists SET ${sets.join(", ")} WHERE id = ?`, params);
5460
+ return getTaskList(id, d);
5461
+ })();
5236
5462
  }
5237
5463
  function deleteTaskList(id, db) {
5238
5464
  const d = db || getDatabase();
@@ -5244,7 +5470,10 @@ function deleteTaskList(id, db) {
5244
5470
  object_id: id,
5245
5471
  payload: list
5246
5472
  }, d);
5247
- return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
5473
+ return d.transaction(() => {
5474
+ releaseCanonicalSlugClaims("task_list", id, d);
5475
+ return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
5476
+ })();
5248
5477
  }
5249
5478
  function ensureTaskList(name, slug, projectId, db) {
5250
5479
  const d = db || getDatabase();
@@ -1,11 +1,11 @@
1
1
  import type { Database } from "bun:sqlite";
2
- import type { CreateProjectInput, CreateProjectSourceInput, Project, ProjectSource } from "../types/index.js";
2
+ import type { CreateProjectInput, CreateProjectSourceInput, Project, ProjectSource, UpdateProjectInput } from "../types/index.js";
3
3
  export declare function slugify(name: string): string;
4
4
  export declare function createProject(input: CreateProjectInput, db?: Database): Project;
5
5
  export declare function getProject(id: string, db?: Database): Project | null;
6
6
  export declare function getProjectByPath(path: string, db?: Database): Project | null;
7
7
  export declare function listProjects(db?: Database): Project[];
8
- export declare function updateProject(id: string, input: Partial<Pick<Project, "name" | "description" | "task_list_id" | "path">>, db?: Database): Project;
8
+ export declare function updateProject(id: string, input: UpdateProjectInput, db?: Database): Project;
9
9
  /**
10
10
  * Rename a project: update name and/or task_list_id (slug).
11
11
  * Cascades slug change to any task_list whose slug matched the old task_list_id.
@@ -1 +1 @@
1
- {"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/db/projects.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAoB,MAAM,YAAY,CAAC;AAC7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,OAAO,EAAE,aAAa,EAAoB,MAAM,mBAAmB,CAAC;AAMhI,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5C;AAyBD,wBAAgB,aAAa,CAC3B,KAAK,EAAE,kBAAkB,EACzB,EAAE,CAAC,EAAE,QAAQ,GACZ,OAAO,CAeT;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,IAAI,CAIpE;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,IAAI,CAc5E;AAED,wBAAgB,YAAY,CAAC,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,EAAE,CAKrD;AAED,wBAAgB,aAAa,CAC3B,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,GAAG,cAAc,GAAG,MAAM,CAAC,CAAC,EAC/E,EAAE,CAAC,EAAE,QAAQ,GACZ,OAAO,CA6BT;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,EAAE,EAAE,MAAM,EACV,KAAK,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,EAC3C,EAAE,CAAC,EAAE,QAAQ,GACZ;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAA;CAAE,CAuClD;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,CAWhE;AAWD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,wBAAwB,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,aAAa,CAW9F;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,CAItE;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,aAAa,EAAE,CAIpF;AAED,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,IAAI,CAM/E;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CAS/E;AAED,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,EAAE,CAAC,EAAE,QAAQ,GACZ,OAAO,CAiBT;AAID,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,kBAAkB,CAwBtG;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CAWnF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,kBAAkB,EAAE,CAK5F;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,CAiBpG"}
1
+ {"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/db/projects.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGtC,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CA8BhD;AA6BD,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CA6oC/C;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CA4BnD"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGtC,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CA8BhD;AA6BD,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CA0uC/C;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CA4BnD"}
@@ -0,0 +1,11 @@
1
+ import type { Database } from "bun:sqlite";
2
+ export type CanonicalSlugKind = "project" | "task_list";
3
+ export declare function taskListSlugScopeKey(projectId: string | null | undefined): string;
4
+ /**
5
+ * Claim a slug for newly-created or explicitly-renamed local records.
6
+ * Legacy rows are deliberately not backfilled: historical duplicates remain
7
+ * readable until an operator reconciles them explicitly.
8
+ */
9
+ export declare function claimCanonicalSlug(kind: CanonicalSlugKind, scopeKey: string, slug: string, objectId: string, db: Database): boolean;
10
+ export declare function releaseCanonicalSlugClaims(kind: CanonicalSlugKind, objectId: string, db: Database): void;
11
+ //# sourceMappingURL=slug-claims.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slug-claims.d.ts","sourceRoot":"","sources":["../../src/db/slug-claims.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,WAAW,CAAC;AAExD,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAEjF;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,iBAAiB,EACvB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,QAAQ,GACX,OAAO,CAUT;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,GAAG,IAAI,CAExG"}
@@ -1 +1 @@
1
- {"version":3,"file":"task-lists.d.ts","sourceRoot":"","sources":["../../src/db/task-lists.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,mBAAmB,EAAE,QAAQ,EAAe,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAazG,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAyBlF;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,IAAI,CAItE;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,IAAI,CASlG;AAED,wBAAgB,aAAa,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,QAAQ,EAAE,CAM3E;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAyB9F;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,CAUjE;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAKtG"}
1
+ {"version":3,"file":"task-lists.d.ts","sourceRoot":"","sources":["../../src/db/task-lists.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,mBAAmB,EAAE,QAAQ,EAAe,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAezG,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAwBlF;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,IAAI,CAItE;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,IAAI,CASlG;AAED,wBAAgB,aAAa,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,QAAQ,EAAE,CAM3E;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,QAAQ,CA4C9F;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,CAajE;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAKtG"}