@hasna/todos 0.11.57 → 0.11.58

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
@@ -6154,7 +6154,7 @@ var init_event_hooks = __esm(() => {
6154
6154
  VALID_TARGETS = new Set(["stdout", "file", "socket", "script"]);
6155
6155
  });
6156
6156
 
6157
- // node_modules/.bun/@hasna+events@0.1.7/node_modules/@hasna/events/dist/index.js
6157
+ // node_modules/.bun/@hasna+events@0.1.9/node_modules/@hasna/events/dist/index.js
6158
6158
  import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
6159
6159
  import { existsSync as existsSync6 } from "fs";
6160
6160
  import { homedir } from "os";
@@ -6171,17 +6171,30 @@ function getPathValue(input, path) {
6171
6171
  return;
6172
6172
  }, input);
6173
6173
  }
6174
- function wildcardToRegExp(pattern) {
6175
- const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
6176
- return new RegExp(`^${escaped}$`);
6174
+ function wildcardToRegExp(pattern, options = {}) {
6175
+ let body = "";
6176
+ for (let index = 0;index < pattern.length; index += 1) {
6177
+ const char = pattern[index];
6178
+ if (char === "*") {
6179
+ if (pattern[index + 1] === "*") {
6180
+ body += ".*";
6181
+ index += 1;
6182
+ } else {
6183
+ body += options.segmentSafe ? "[^/]*" : ".*";
6184
+ }
6185
+ } else {
6186
+ body += char.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
6187
+ }
6188
+ }
6189
+ return new RegExp(`^${body}$`);
6177
6190
  }
6178
- function matchString(value, matcher) {
6191
+ function matchString(value, matcher, options = {}) {
6179
6192
  if (matcher === undefined)
6180
6193
  return true;
6181
6194
  if (value === undefined)
6182
6195
  return false;
6183
6196
  const matchers = Array.isArray(matcher) ? matcher : [matcher];
6184
- return matchers.some((item) => wildcardToRegExp(item).test(value));
6197
+ return matchers.some((item) => wildcardToRegExp(item, options).test(value));
6185
6198
  }
6186
6199
  function matchRecord(input, matcher) {
6187
6200
  if (!matcher)
@@ -6189,7 +6202,9 @@ function matchRecord(input, matcher) {
6189
6202
  return Object.entries(matcher).every(([path, expected]) => {
6190
6203
  const actual = getPathValue(input, path);
6191
6204
  if (typeof expected === "string" || Array.isArray(expected)) {
6192
- return matchString(actual === undefined ? undefined : String(actual), expected);
6205
+ return matchString(actual === undefined ? undefined : String(actual), expected, {
6206
+ segmentSafe: path.endsWith("_path") || path.endsWith(".path")
6207
+ });
6193
6208
  }
6194
6209
  return actual === expected;
6195
6210
  });
@@ -6543,7 +6558,7 @@ class EventsClient {
6543
6558
  }
6544
6559
  return deliveries;
6545
6560
  }
6546
- async testChannel(id, input = {}) {
6561
+ async matchChannel(id, input = {}) {
6547
6562
  const channel = await this.store.getChannel(id);
6548
6563
  if (!channel)
6549
6564
  throw new Error(`Channel not found: ${id}`);
@@ -6560,6 +6575,34 @@ class EventsClient {
6560
6575
  time: input.time,
6561
6576
  id: input.id
6562
6577
  });
6578
+ const matched = channelMatchesEvent(channel, event);
6579
+ return {
6580
+ channelId: channel.id,
6581
+ matched,
6582
+ event,
6583
+ filters: channel.filters,
6584
+ reason: matched ? undefined : channel.enabled ? "event did not match channel filters" : "channel is disabled"
6585
+ };
6586
+ }
6587
+ async testChannel(id, input = {}, options = {}) {
6588
+ const channel = await this.store.getChannel(id);
6589
+ if (!channel)
6590
+ throw new Error(`Channel not found: ${id}`);
6591
+ const match = await this.matchChannel(id, input);
6592
+ const event = match.event;
6593
+ if (options.honorFilters && !match.matched) {
6594
+ const timestamp = new Date().toISOString();
6595
+ const result2 = createDeliveryResult(event, channel, [{
6596
+ attempt: 1,
6597
+ status: "skipped",
6598
+ startedAt: timestamp,
6599
+ completedAt: timestamp,
6600
+ error: match.reason
6601
+ }]);
6602
+ result2.metadata = { reason: "filter_mismatch" };
6603
+ await this.store.appendDelivery(result2);
6604
+ return result2;
6605
+ }
6563
6606
  const eventForChannel = await this.applyRedaction(event, channel);
6564
6607
  const result = await this.deliverWithRetry(eventForChannel, channel);
6565
6608
  await this.store.appendDelivery(result);
@@ -6663,6 +6706,90 @@ var init_dist = __esm(() => {
6663
6706
  DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
6664
6707
  });
6665
6708
 
6709
+ // src/db/task-lists.ts
6710
+ function rowToTaskList(row) {
6711
+ return {
6712
+ ...row,
6713
+ metadata: JSON.parse(row.metadata || "{}")
6714
+ };
6715
+ }
6716
+ function createTaskList(input, db) {
6717
+ const d = db || getDatabase();
6718
+ const id = uuid();
6719
+ const timestamp = now();
6720
+ const slug = input.slug || slugify(input.name);
6721
+ if (!input.project_id) {
6722
+ const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
6723
+ if (existing) {
6724
+ throw new Error(`Standalone task list with slug "${slug}" already exists`);
6725
+ }
6726
+ }
6727
+ d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
6728
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
6729
+ return getTaskList(id, d);
6730
+ }
6731
+ function getTaskList(id, db) {
6732
+ const d = db || getDatabase();
6733
+ const row = d.query("SELECT * FROM task_lists WHERE id = ?").get(id);
6734
+ return row ? rowToTaskList(row) : null;
6735
+ }
6736
+ function getTaskListBySlug(slug, projectId, db) {
6737
+ const d = db || getDatabase();
6738
+ let row;
6739
+ if (projectId) {
6740
+ row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id = ?").get(slug, projectId);
6741
+ } else {
6742
+ row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id IS NULL").get(slug);
6743
+ }
6744
+ return row ? rowToTaskList(row) : null;
6745
+ }
6746
+ function listTaskLists(projectId, db) {
6747
+ const d = db || getDatabase();
6748
+ if (projectId) {
6749
+ return d.query("SELECT * FROM task_lists WHERE project_id = ? ORDER BY name").all(projectId).map(rowToTaskList);
6750
+ }
6751
+ return d.query("SELECT * FROM task_lists ORDER BY name").all().map(rowToTaskList);
6752
+ }
6753
+ function updateTaskList(id, input, db) {
6754
+ const d = db || getDatabase();
6755
+ const existing = getTaskList(id, d);
6756
+ if (!existing)
6757
+ throw new TaskListNotFoundError(id);
6758
+ const sets = ["updated_at = ?"];
6759
+ const params = [now()];
6760
+ if (input.name !== undefined) {
6761
+ sets.push("name = ?");
6762
+ params.push(input.name);
6763
+ }
6764
+ if (input.description !== undefined) {
6765
+ sets.push("description = ?");
6766
+ params.push(input.description);
6767
+ }
6768
+ if (input.metadata !== undefined) {
6769
+ sets.push("metadata = ?");
6770
+ params.push(JSON.stringify(input.metadata));
6771
+ }
6772
+ params.push(id);
6773
+ d.run(`UPDATE task_lists SET ${sets.join(", ")} WHERE id = ?`, params);
6774
+ return getTaskList(id, d);
6775
+ }
6776
+ function deleteTaskList(id, db) {
6777
+ const d = db || getDatabase();
6778
+ return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
6779
+ }
6780
+ function ensureTaskList(name, slug, projectId, db) {
6781
+ const d = db || getDatabase();
6782
+ const existing = getTaskListBySlug(slug, projectId, d);
6783
+ if (existing)
6784
+ return existing;
6785
+ return createTaskList({ name, slug, project_id: projectId }, d);
6786
+ }
6787
+ var init_task_lists = __esm(() => {
6788
+ init_types();
6789
+ init_database();
6790
+ init_projects();
6791
+ });
6792
+
6666
6793
  // src/lib/shared-events.ts
6667
6794
  function taskEventData(task, extra = {}) {
6668
6795
  return {
@@ -6692,6 +6819,69 @@ function taskEventData(task, extra = {}) {
6692
6819
  ...extra
6693
6820
  };
6694
6821
  }
6822
+ function taskEventMetadata(task) {
6823
+ const metadata = {
6824
+ package: "@hasna/todos",
6825
+ todos_event_schema_version: 1,
6826
+ task_id: task.id,
6827
+ task_short_id: task.short_id,
6828
+ project_id: task.project_id,
6829
+ task_list_id: task.task_list_id,
6830
+ working_dir: task.working_dir
6831
+ };
6832
+ try {
6833
+ const project = task.project_id ? getProject(task.project_id) : null;
6834
+ const projectPath = project ? readMachineLocalPath(project) ?? project.path : task.working_dir;
6835
+ if (project) {
6836
+ metadata.project_id = project.id;
6837
+ metadata.project_name = project.name;
6838
+ metadata.project_path = projectPath;
6839
+ metadata.project_canonical_path = project.path;
6840
+ metadata.project_default_task_list_slug = project.task_list_id;
6841
+ metadata.root_project_id = inferRootProjectId(project);
6842
+ } else if (projectPath) {
6843
+ metadata.project_path = projectPath;
6844
+ metadata.project_canonical_path = projectPath;
6845
+ }
6846
+ if (projectPath) {
6847
+ metadata.project_kind = classifyProjectKind(projectPath);
6848
+ metadata.project_is_worktree = isWorktreePath(projectPath);
6849
+ if (typeof task.metadata.route_enabled === "boolean") {
6850
+ metadata.route_enabled = task.metadata.route_enabled;
6851
+ }
6852
+ metadata.working_dir = task.working_dir ?? projectPath;
6853
+ }
6854
+ const taskList = task.task_list_id ? getTaskList(task.task_list_id) ?? (project ? getTaskListBySlug(task.task_list_id, project.id) : null) : project?.task_list_id ? getTaskListBySlug(project.task_list_id, project.id) : null;
6855
+ if (taskList) {
6856
+ metadata.task_list_id = taskList.id;
6857
+ metadata.task_list_slug = taskList.slug;
6858
+ metadata.task_list_name = taskList.name;
6859
+ metadata.task_list_project_id = taskList.project_id;
6860
+ metadata.task_list_is_project_default = Boolean(project?.task_list_id && taskList.slug === project.task_list_id);
6861
+ }
6862
+ } catch {}
6863
+ return metadata;
6864
+ }
6865
+ function classifyProjectKind(path) {
6866
+ return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
6867
+ }
6868
+ function isWorktreePath(path) {
6869
+ return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
6870
+ }
6871
+ function inferRootProjectId(project) {
6872
+ return isWorktreePath(project.path) ? null : project.id;
6873
+ }
6874
+ function readMachineLocalPath(project) {
6875
+ const machineId = process.env["TODOS_MACHINE_ID"];
6876
+ if (!machineId)
6877
+ return null;
6878
+ try {
6879
+ const row = getDatabase().query("SELECT path FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(project.id, machineId);
6880
+ return row?.path ?? null;
6881
+ } catch {
6882
+ return null;
6883
+ }
6884
+ }
6695
6885
  async function emitSharedTaskEvent(input) {
6696
6886
  const data = taskEventData(input.task, input.data);
6697
6887
  await new EventsClient().emit({
@@ -6702,12 +6892,7 @@ async function emitSharedTaskEvent(input) {
6702
6892
  message: input.message ?? `${input.type}: ${input.task.title}`,
6703
6893
  data,
6704
6894
  dedupeKey: input.dedupeKey ?? `${input.type}:${input.task.id}:${input.task.version}`,
6705
- metadata: {
6706
- package: "@hasna/todos",
6707
- task_id: input.task.id,
6708
- project_id: input.task.project_id,
6709
- task_list_id: input.task.task_list_id
6710
- }
6895
+ metadata: taskEventMetadata(input.task)
6711
6896
  }, { deliver: true, dedupe: true });
6712
6897
  }
6713
6898
  function emitSharedTaskEventQuiet(input) {
@@ -6718,6 +6903,9 @@ function emitSharedTaskEventQuiet(input) {
6718
6903
  var SOURCE = "todos";
6719
6904
  var init_shared_events = __esm(() => {
6720
6905
  init_dist();
6906
+ init_database();
6907
+ init_projects();
6908
+ init_task_lists();
6721
6909
  });
6722
6910
 
6723
6911
  // src/lib/secret-redaction.ts
@@ -14207,90 +14395,6 @@ var init_sync = __esm(() => {
14207
14395
  init_config();
14208
14396
  });
14209
14397
 
14210
- // src/db/task-lists.ts
14211
- function rowToTaskList(row) {
14212
- return {
14213
- ...row,
14214
- metadata: JSON.parse(row.metadata || "{}")
14215
- };
14216
- }
14217
- function createTaskList(input, db) {
14218
- const d = db || getDatabase();
14219
- const id = uuid();
14220
- const timestamp = now();
14221
- const slug = input.slug || slugify(input.name);
14222
- if (!input.project_id) {
14223
- const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
14224
- if (existing) {
14225
- throw new Error(`Standalone task list with slug "${slug}" already exists`);
14226
- }
14227
- }
14228
- d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
14229
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
14230
- return getTaskList(id, d);
14231
- }
14232
- function getTaskList(id, db) {
14233
- const d = db || getDatabase();
14234
- const row = d.query("SELECT * FROM task_lists WHERE id = ?").get(id);
14235
- return row ? rowToTaskList(row) : null;
14236
- }
14237
- function getTaskListBySlug(slug, projectId, db) {
14238
- const d = db || getDatabase();
14239
- let row;
14240
- if (projectId) {
14241
- row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id = ?").get(slug, projectId);
14242
- } else {
14243
- row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id IS NULL").get(slug);
14244
- }
14245
- return row ? rowToTaskList(row) : null;
14246
- }
14247
- function listTaskLists(projectId, db) {
14248
- const d = db || getDatabase();
14249
- if (projectId) {
14250
- return d.query("SELECT * FROM task_lists WHERE project_id = ? ORDER BY name").all(projectId).map(rowToTaskList);
14251
- }
14252
- return d.query("SELECT * FROM task_lists ORDER BY name").all().map(rowToTaskList);
14253
- }
14254
- function updateTaskList(id, input, db) {
14255
- const d = db || getDatabase();
14256
- const existing = getTaskList(id, d);
14257
- if (!existing)
14258
- throw new TaskListNotFoundError(id);
14259
- const sets = ["updated_at = ?"];
14260
- const params = [now()];
14261
- if (input.name !== undefined) {
14262
- sets.push("name = ?");
14263
- params.push(input.name);
14264
- }
14265
- if (input.description !== undefined) {
14266
- sets.push("description = ?");
14267
- params.push(input.description);
14268
- }
14269
- if (input.metadata !== undefined) {
14270
- sets.push("metadata = ?");
14271
- params.push(JSON.stringify(input.metadata));
14272
- }
14273
- params.push(id);
14274
- d.run(`UPDATE task_lists SET ${sets.join(", ")} WHERE id = ?`, params);
14275
- return getTaskList(id, d);
14276
- }
14277
- function deleteTaskList(id, db) {
14278
- const d = db || getDatabase();
14279
- return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
14280
- }
14281
- function ensureTaskList(name, slug, projectId, db) {
14282
- const d = db || getDatabase();
14283
- const existing = getTaskListBySlug(slug, projectId, d);
14284
- if (existing)
14285
- return existing;
14286
- return createTaskList({ name, slug, project_id: projectId }, d);
14287
- }
14288
- var init_task_lists = __esm(() => {
14289
- init_types();
14290
- init_database();
14291
- init_projects();
14292
- });
14293
-
14294
14398
  // src/lib/project-bootstrap.ts
14295
14399
  var exports_project_bootstrap = {};
14296
14400
  __export(exports_project_bootstrap, {
package/dist/contracts.js CHANGED
@@ -6773,7 +6773,7 @@ async function testLocalEventHook(name, input) {
6773
6773
  return emitLocalEventHooks({ ...input, hooks: [hook] });
6774
6774
  }
6775
6775
 
6776
- // node_modules/.bun/@hasna+events@0.1.7/node_modules/@hasna/events/dist/index.js
6776
+ // node_modules/.bun/@hasna+events@0.1.9/node_modules/@hasna/events/dist/index.js
6777
6777
  import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
6778
6778
  import { existsSync as existsSync6 } from "fs";
6779
6779
  import { homedir } from "os";
@@ -6790,17 +6790,30 @@ function getPathValue(input, path) {
6790
6790
  return;
6791
6791
  }, input);
6792
6792
  }
6793
- function wildcardToRegExp(pattern) {
6794
- const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
6795
- return new RegExp(`^${escaped}$`);
6793
+ function wildcardToRegExp(pattern, options = {}) {
6794
+ let body = "";
6795
+ for (let index = 0;index < pattern.length; index += 1) {
6796
+ const char = pattern[index];
6797
+ if (char === "*") {
6798
+ if (pattern[index + 1] === "*") {
6799
+ body += ".*";
6800
+ index += 1;
6801
+ } else {
6802
+ body += options.segmentSafe ? "[^/]*" : ".*";
6803
+ }
6804
+ } else {
6805
+ body += char.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
6806
+ }
6807
+ }
6808
+ return new RegExp(`^${body}$`);
6796
6809
  }
6797
- function matchString(value, matcher) {
6810
+ function matchString(value, matcher, options = {}) {
6798
6811
  if (matcher === undefined)
6799
6812
  return true;
6800
6813
  if (value === undefined)
6801
6814
  return false;
6802
6815
  const matchers = Array.isArray(matcher) ? matcher : [matcher];
6803
- return matchers.some((item) => wildcardToRegExp(item).test(value));
6816
+ return matchers.some((item) => wildcardToRegExp(item, options).test(value));
6804
6817
  }
6805
6818
  function matchRecord(input, matcher) {
6806
6819
  if (!matcher)
@@ -6808,7 +6821,9 @@ function matchRecord(input, matcher) {
6808
6821
  return Object.entries(matcher).every(([path, expected]) => {
6809
6822
  const actual = getPathValue(input, path);
6810
6823
  if (typeof expected === "string" || Array.isArray(expected)) {
6811
- return matchString(actual === undefined ? undefined : String(actual), expected);
6824
+ return matchString(actual === undefined ? undefined : String(actual), expected, {
6825
+ segmentSafe: path.endsWith("_path") || path.endsWith(".path")
6826
+ });
6812
6827
  }
6813
6828
  return actual === expected;
6814
6829
  });
@@ -6828,7 +6843,6 @@ var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
6828
6843
  function getEventsDataDir(override) {
6829
6844
  return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join5(homedir(), ".hasna", "events");
6830
6845
  }
6831
-
6832
6846
  class JsonEventsStore {
6833
6847
  dataDir;
6834
6848
  channelsPath;
@@ -7165,7 +7179,7 @@ class EventsClient {
7165
7179
  }
7166
7180
  return deliveries;
7167
7181
  }
7168
- async testChannel(id, input = {}) {
7182
+ async matchChannel(id, input = {}) {
7169
7183
  const channel = await this.store.getChannel(id);
7170
7184
  if (!channel)
7171
7185
  throw new Error(`Channel not found: ${id}`);
@@ -7182,6 +7196,34 @@ class EventsClient {
7182
7196
  time: input.time,
7183
7197
  id: input.id
7184
7198
  });
7199
+ const matched = channelMatchesEvent(channel, event);
7200
+ return {
7201
+ channelId: channel.id,
7202
+ matched,
7203
+ event,
7204
+ filters: channel.filters,
7205
+ reason: matched ? undefined : channel.enabled ? "event did not match channel filters" : "channel is disabled"
7206
+ };
7207
+ }
7208
+ async testChannel(id, input = {}, options = {}) {
7209
+ const channel = await this.store.getChannel(id);
7210
+ if (!channel)
7211
+ throw new Error(`Channel not found: ${id}`);
7212
+ const match = await this.matchChannel(id, input);
7213
+ const event = match.event;
7214
+ if (options.honorFilters && !match.matched) {
7215
+ const timestamp = new Date().toISOString();
7216
+ const result2 = createDeliveryResult(event, channel, [{
7217
+ attempt: 1,
7218
+ status: "skipped",
7219
+ startedAt: timestamp,
7220
+ completedAt: timestamp,
7221
+ error: match.reason
7222
+ }]);
7223
+ result2.metadata = { reason: "filter_mismatch" };
7224
+ await this.store.appendDelivery(result2);
7225
+ return result2;
7226
+ }
7185
7227
  const eventForChannel = await this.applyRedaction(event, channel);
7186
7228
  const result = await this.deliverWithRetry(eventForChannel, channel);
7187
7229
  await this.store.appendDelivery(result);
@@ -7281,6 +7323,90 @@ function normalizeRetryPolicy(policy) {
7281
7323
  };
7282
7324
  }
7283
7325
 
7326
+ // src/lib/shared-events.ts
7327
+ init_database();
7328
+
7329
+ // src/db/task-lists.ts
7330
+ init_types();
7331
+ init_database();
7332
+ function rowToTaskList(row) {
7333
+ return {
7334
+ ...row,
7335
+ metadata: JSON.parse(row.metadata || "{}")
7336
+ };
7337
+ }
7338
+ function createTaskList(input, db) {
7339
+ const d = db || getDatabase();
7340
+ const id = uuid();
7341
+ const timestamp = now();
7342
+ const slug = input.slug || slugify(input.name);
7343
+ if (!input.project_id) {
7344
+ const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
7345
+ if (existing) {
7346
+ throw new Error(`Standalone task list with slug "${slug}" already exists`);
7347
+ }
7348
+ }
7349
+ d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
7350
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
7351
+ return getTaskList(id, d);
7352
+ }
7353
+ function getTaskList(id, db) {
7354
+ const d = db || getDatabase();
7355
+ const row = d.query("SELECT * FROM task_lists WHERE id = ?").get(id);
7356
+ return row ? rowToTaskList(row) : null;
7357
+ }
7358
+ function getTaskListBySlug(slug, projectId, db) {
7359
+ const d = db || getDatabase();
7360
+ let row;
7361
+ if (projectId) {
7362
+ row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id = ?").get(slug, projectId);
7363
+ } else {
7364
+ row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id IS NULL").get(slug);
7365
+ }
7366
+ return row ? rowToTaskList(row) : null;
7367
+ }
7368
+ function listTaskLists(projectId, db) {
7369
+ const d = db || getDatabase();
7370
+ if (projectId) {
7371
+ return d.query("SELECT * FROM task_lists WHERE project_id = ? ORDER BY name").all(projectId).map(rowToTaskList);
7372
+ }
7373
+ return d.query("SELECT * FROM task_lists ORDER BY name").all().map(rowToTaskList);
7374
+ }
7375
+ function updateTaskList(id, input, db) {
7376
+ const d = db || getDatabase();
7377
+ const existing = getTaskList(id, d);
7378
+ if (!existing)
7379
+ throw new TaskListNotFoundError(id);
7380
+ const sets = ["updated_at = ?"];
7381
+ const params = [now()];
7382
+ if (input.name !== undefined) {
7383
+ sets.push("name = ?");
7384
+ params.push(input.name);
7385
+ }
7386
+ if (input.description !== undefined) {
7387
+ sets.push("description = ?");
7388
+ params.push(input.description);
7389
+ }
7390
+ if (input.metadata !== undefined) {
7391
+ sets.push("metadata = ?");
7392
+ params.push(JSON.stringify(input.metadata));
7393
+ }
7394
+ params.push(id);
7395
+ d.run(`UPDATE task_lists SET ${sets.join(", ")} WHERE id = ?`, params);
7396
+ return getTaskList(id, d);
7397
+ }
7398
+ function deleteTaskList(id, db) {
7399
+ const d = db || getDatabase();
7400
+ return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
7401
+ }
7402
+ function ensureTaskList(name, slug, projectId, db) {
7403
+ const d = db || getDatabase();
7404
+ const existing = getTaskListBySlug(slug, projectId, d);
7405
+ if (existing)
7406
+ return existing;
7407
+ return createTaskList({ name, slug, project_id: projectId }, d);
7408
+ }
7409
+
7284
7410
  // src/lib/shared-events.ts
7285
7411
  var SOURCE = "todos";
7286
7412
  function taskEventData(task, extra = {}) {
@@ -7311,6 +7437,69 @@ function taskEventData(task, extra = {}) {
7311
7437
  ...extra
7312
7438
  };
7313
7439
  }
7440
+ function taskEventMetadata(task) {
7441
+ const metadata = {
7442
+ package: "@hasna/todos",
7443
+ todos_event_schema_version: 1,
7444
+ task_id: task.id,
7445
+ task_short_id: task.short_id,
7446
+ project_id: task.project_id,
7447
+ task_list_id: task.task_list_id,
7448
+ working_dir: task.working_dir
7449
+ };
7450
+ try {
7451
+ const project = task.project_id ? getProject(task.project_id) : null;
7452
+ const projectPath = project ? readMachineLocalPath(project) ?? project.path : task.working_dir;
7453
+ if (project) {
7454
+ metadata.project_id = project.id;
7455
+ metadata.project_name = project.name;
7456
+ metadata.project_path = projectPath;
7457
+ metadata.project_canonical_path = project.path;
7458
+ metadata.project_default_task_list_slug = project.task_list_id;
7459
+ metadata.root_project_id = inferRootProjectId(project);
7460
+ } else if (projectPath) {
7461
+ metadata.project_path = projectPath;
7462
+ metadata.project_canonical_path = projectPath;
7463
+ }
7464
+ if (projectPath) {
7465
+ metadata.project_kind = classifyProjectKind(projectPath);
7466
+ metadata.project_is_worktree = isWorktreePath(projectPath);
7467
+ if (typeof task.metadata.route_enabled === "boolean") {
7468
+ metadata.route_enabled = task.metadata.route_enabled;
7469
+ }
7470
+ metadata.working_dir = task.working_dir ?? projectPath;
7471
+ }
7472
+ const taskList = task.task_list_id ? getTaskList(task.task_list_id) ?? (project ? getTaskListBySlug(task.task_list_id, project.id) : null) : project?.task_list_id ? getTaskListBySlug(project.task_list_id, project.id) : null;
7473
+ if (taskList) {
7474
+ metadata.task_list_id = taskList.id;
7475
+ metadata.task_list_slug = taskList.slug;
7476
+ metadata.task_list_name = taskList.name;
7477
+ metadata.task_list_project_id = taskList.project_id;
7478
+ metadata.task_list_is_project_default = Boolean(project?.task_list_id && taskList.slug === project.task_list_id);
7479
+ }
7480
+ } catch {}
7481
+ return metadata;
7482
+ }
7483
+ function classifyProjectKind(path) {
7484
+ return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
7485
+ }
7486
+ function isWorktreePath(path) {
7487
+ return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
7488
+ }
7489
+ function inferRootProjectId(project) {
7490
+ return isWorktreePath(project.path) ? null : project.id;
7491
+ }
7492
+ function readMachineLocalPath(project) {
7493
+ const machineId = process.env["TODOS_MACHINE_ID"];
7494
+ if (!machineId)
7495
+ return null;
7496
+ try {
7497
+ const row = getDatabase().query("SELECT path FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(project.id, machineId);
7498
+ return row?.path ?? null;
7499
+ } catch {
7500
+ return null;
7501
+ }
7502
+ }
7314
7503
  async function emitSharedTaskEvent(input) {
7315
7504
  const data = taskEventData(input.task, input.data);
7316
7505
  await new EventsClient().emit({
@@ -7321,12 +7510,7 @@ async function emitSharedTaskEvent(input) {
7321
7510
  message: input.message ?? `${input.type}: ${input.task.title}`,
7322
7511
  data,
7323
7512
  dedupeKey: input.dedupeKey ?? `${input.type}:${input.task.id}:${input.task.version}`,
7324
- metadata: {
7325
- package: "@hasna/todos",
7326
- task_id: input.task.id,
7327
- project_id: input.task.project_id,
7328
- task_list_id: input.task.task_list_id
7329
- }
7513
+ metadata: taskEventMetadata(input.task)
7330
7514
  }, { deliver: true, dedupe: true });
7331
7515
  }
7332
7516
  function emitSharedTaskEventQuiet(input) {
@@ -11644,7 +11828,7 @@ function rowToTask2(row) {
11644
11828
  requires_approval: Boolean(row.requires_approval)
11645
11829
  };
11646
11830
  }
11647
- function rowToTaskList(row) {
11831
+ function rowToTaskList2(row) {
11648
11832
  return { ...row, metadata: parseJsonObject3(row.metadata) };
11649
11833
  }
11650
11834
  function rowWithMetadata(row) {
@@ -11680,7 +11864,7 @@ function createLocalBridgeBundle(options = {}, db) {
11680
11864
  const project = options.project_id ? d.query("SELECT * FROM projects WHERE id = ?").get(options.project_id) : null;
11681
11865
  const data = redactValue({
11682
11866
  projects: options.project_id ? project ? [project] : [] : d.query("SELECT * FROM projects ORDER BY name").all(),
11683
- task_lists: (options.project_id ? d.query("SELECT * FROM task_lists WHERE project_id = ? ORDER BY name").all(options.project_id) : d.query("SELECT * FROM task_lists ORDER BY name").all()).map(rowToTaskList),
11867
+ task_lists: (options.project_id ? d.query("SELECT * FROM task_lists WHERE project_id = ? ORDER BY name").all(options.project_id) : d.query("SELECT * FROM task_lists ORDER BY name").all()).map(rowToTaskList2),
11684
11868
  plans: options.project_id ? d.query("SELECT * FROM plans WHERE project_id = ? ORDER BY created_at").all(options.project_id) : d.query("SELECT * FROM plans ORDER BY created_at").all(),
11685
11869
  tasks: queryByTaskIds(d, "SELECT * FROM tasks WHERE id IN (__TASK_IDS__) ORDER BY created_at", taskIds).map(rowToTask2),
11686
11870
  task_dependencies: queryByTaskIds(d, "SELECT task_id, depends_on, external_project_id, external_task_id FROM task_dependencies WHERE task_id IN (__TASK_IDS__) ORDER BY task_id, depends_on", taskIds),