@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/mcp/index.js CHANGED
@@ -8653,7 +8653,7 @@ var init_event_hooks = __esm(() => {
8653
8653
  VALID_TARGETS = new Set(["stdout", "file", "socket", "script"]);
8654
8654
  });
8655
8655
 
8656
- // node_modules/.bun/@hasna+events@0.1.7/node_modules/@hasna/events/dist/index.js
8656
+ // node_modules/.bun/@hasna+events@0.1.9/node_modules/@hasna/events/dist/index.js
8657
8657
  import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
8658
8658
  import { existsSync as existsSync5 } from "fs";
8659
8659
  import { homedir } from "os";
@@ -8670,17 +8670,30 @@ function getPathValue(input, path) {
8670
8670
  return;
8671
8671
  }, input);
8672
8672
  }
8673
- function wildcardToRegExp(pattern) {
8674
- const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
8675
- return new RegExp(`^${escaped}$`);
8673
+ function wildcardToRegExp(pattern, options = {}) {
8674
+ let body = "";
8675
+ for (let index = 0;index < pattern.length; index += 1) {
8676
+ const char = pattern[index];
8677
+ if (char === "*") {
8678
+ if (pattern[index + 1] === "*") {
8679
+ body += ".*";
8680
+ index += 1;
8681
+ } else {
8682
+ body += options.segmentSafe ? "[^/]*" : ".*";
8683
+ }
8684
+ } else {
8685
+ body += char.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
8686
+ }
8687
+ }
8688
+ return new RegExp(`^${body}$`);
8676
8689
  }
8677
- function matchString(value, matcher) {
8690
+ function matchString(value, matcher, options = {}) {
8678
8691
  if (matcher === undefined)
8679
8692
  return true;
8680
8693
  if (value === undefined)
8681
8694
  return false;
8682
8695
  const matchers = Array.isArray(matcher) ? matcher : [matcher];
8683
- return matchers.some((item) => wildcardToRegExp(item).test(value));
8696
+ return matchers.some((item) => wildcardToRegExp(item, options).test(value));
8684
8697
  }
8685
8698
  function matchRecord(input, matcher) {
8686
8699
  if (!matcher)
@@ -8688,7 +8701,9 @@ function matchRecord(input, matcher) {
8688
8701
  return Object.entries(matcher).every(([path, expected]) => {
8689
8702
  const actual = getPathValue(input, path);
8690
8703
  if (typeof expected === "string" || Array.isArray(expected)) {
8691
- return matchString(actual === undefined ? undefined : String(actual), expected);
8704
+ return matchString(actual === undefined ? undefined : String(actual), expected, {
8705
+ segmentSafe: path.endsWith("_path") || path.endsWith(".path")
8706
+ });
8692
8707
  }
8693
8708
  return actual === expected;
8694
8709
  });
@@ -9042,7 +9057,7 @@ class EventsClient {
9042
9057
  }
9043
9058
  return deliveries;
9044
9059
  }
9045
- async testChannel(id, input = {}) {
9060
+ async matchChannel(id, input = {}) {
9046
9061
  const channel = await this.store.getChannel(id);
9047
9062
  if (!channel)
9048
9063
  throw new Error(`Channel not found: ${id}`);
@@ -9059,6 +9074,34 @@ class EventsClient {
9059
9074
  time: input.time,
9060
9075
  id: input.id
9061
9076
  });
9077
+ const matched = channelMatchesEvent(channel, event);
9078
+ return {
9079
+ channelId: channel.id,
9080
+ matched,
9081
+ event,
9082
+ filters: channel.filters,
9083
+ reason: matched ? undefined : channel.enabled ? "event did not match channel filters" : "channel is disabled"
9084
+ };
9085
+ }
9086
+ async testChannel(id, input = {}, options = {}) {
9087
+ const channel = await this.store.getChannel(id);
9088
+ if (!channel)
9089
+ throw new Error(`Channel not found: ${id}`);
9090
+ const match = await this.matchChannel(id, input);
9091
+ const event = match.event;
9092
+ if (options.honorFilters && !match.matched) {
9093
+ const timestamp = new Date().toISOString();
9094
+ const result2 = createDeliveryResult(event, channel, [{
9095
+ attempt: 1,
9096
+ status: "skipped",
9097
+ startedAt: timestamp,
9098
+ completedAt: timestamp,
9099
+ error: match.reason
9100
+ }]);
9101
+ result2.metadata = { reason: "filter_mismatch" };
9102
+ await this.store.appendDelivery(result2);
9103
+ return result2;
9104
+ }
9062
9105
  const eventForChannel = await this.applyRedaction(event, channel);
9063
9106
  const result = await this.deliverWithRetry(eventForChannel, channel);
9064
9107
  await this.store.appendDelivery(result);
@@ -9162,6 +9205,90 @@ var init_dist = __esm(() => {
9162
9205
  DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
9163
9206
  });
9164
9207
 
9208
+ // src/db/task-lists.ts
9209
+ function rowToTaskList(row) {
9210
+ return {
9211
+ ...row,
9212
+ metadata: JSON.parse(row.metadata || "{}")
9213
+ };
9214
+ }
9215
+ function createTaskList(input, db) {
9216
+ const d = db || getDatabase();
9217
+ const id = uuid();
9218
+ const timestamp = now();
9219
+ const slug = input.slug || slugify(input.name);
9220
+ if (!input.project_id) {
9221
+ const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
9222
+ if (existing) {
9223
+ throw new Error(`Standalone task list with slug "${slug}" already exists`);
9224
+ }
9225
+ }
9226
+ d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
9227
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
9228
+ return getTaskList(id, d);
9229
+ }
9230
+ function getTaskList(id, db) {
9231
+ const d = db || getDatabase();
9232
+ const row = d.query("SELECT * FROM task_lists WHERE id = ?").get(id);
9233
+ return row ? rowToTaskList(row) : null;
9234
+ }
9235
+ function getTaskListBySlug(slug, projectId, db) {
9236
+ const d = db || getDatabase();
9237
+ let row;
9238
+ if (projectId) {
9239
+ row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id = ?").get(slug, projectId);
9240
+ } else {
9241
+ row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id IS NULL").get(slug);
9242
+ }
9243
+ return row ? rowToTaskList(row) : null;
9244
+ }
9245
+ function listTaskLists(projectId, db) {
9246
+ const d = db || getDatabase();
9247
+ if (projectId) {
9248
+ return d.query("SELECT * FROM task_lists WHERE project_id = ? ORDER BY name").all(projectId).map(rowToTaskList);
9249
+ }
9250
+ return d.query("SELECT * FROM task_lists ORDER BY name").all().map(rowToTaskList);
9251
+ }
9252
+ function updateTaskList(id, input, db) {
9253
+ const d = db || getDatabase();
9254
+ const existing = getTaskList(id, d);
9255
+ if (!existing)
9256
+ throw new TaskListNotFoundError(id);
9257
+ const sets = ["updated_at = ?"];
9258
+ const params = [now()];
9259
+ if (input.name !== undefined) {
9260
+ sets.push("name = ?");
9261
+ params.push(input.name);
9262
+ }
9263
+ if (input.description !== undefined) {
9264
+ sets.push("description = ?");
9265
+ params.push(input.description);
9266
+ }
9267
+ if (input.metadata !== undefined) {
9268
+ sets.push("metadata = ?");
9269
+ params.push(JSON.stringify(input.metadata));
9270
+ }
9271
+ params.push(id);
9272
+ d.run(`UPDATE task_lists SET ${sets.join(", ")} WHERE id = ?`, params);
9273
+ return getTaskList(id, d);
9274
+ }
9275
+ function deleteTaskList(id, db) {
9276
+ const d = db || getDatabase();
9277
+ return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
9278
+ }
9279
+ function ensureTaskList(name, slug, projectId, db) {
9280
+ const d = db || getDatabase();
9281
+ const existing = getTaskListBySlug(slug, projectId, d);
9282
+ if (existing)
9283
+ return existing;
9284
+ return createTaskList({ name, slug, project_id: projectId }, d);
9285
+ }
9286
+ var init_task_lists = __esm(() => {
9287
+ init_types();
9288
+ init_database();
9289
+ init_projects();
9290
+ });
9291
+
9165
9292
  // src/lib/shared-events.ts
9166
9293
  function taskEventData(task, extra = {}) {
9167
9294
  return {
@@ -9191,6 +9318,69 @@ function taskEventData(task, extra = {}) {
9191
9318
  ...extra
9192
9319
  };
9193
9320
  }
9321
+ function taskEventMetadata(task) {
9322
+ const metadata = {
9323
+ package: "@hasna/todos",
9324
+ todos_event_schema_version: 1,
9325
+ task_id: task.id,
9326
+ task_short_id: task.short_id,
9327
+ project_id: task.project_id,
9328
+ task_list_id: task.task_list_id,
9329
+ working_dir: task.working_dir
9330
+ };
9331
+ try {
9332
+ const project = task.project_id ? getProject(task.project_id) : null;
9333
+ const projectPath = project ? readMachineLocalPath(project) ?? project.path : task.working_dir;
9334
+ if (project) {
9335
+ metadata.project_id = project.id;
9336
+ metadata.project_name = project.name;
9337
+ metadata.project_path = projectPath;
9338
+ metadata.project_canonical_path = project.path;
9339
+ metadata.project_default_task_list_slug = project.task_list_id;
9340
+ metadata.root_project_id = inferRootProjectId(project);
9341
+ } else if (projectPath) {
9342
+ metadata.project_path = projectPath;
9343
+ metadata.project_canonical_path = projectPath;
9344
+ }
9345
+ if (projectPath) {
9346
+ metadata.project_kind = classifyProjectKind(projectPath);
9347
+ metadata.project_is_worktree = isWorktreePath(projectPath);
9348
+ if (typeof task.metadata.route_enabled === "boolean") {
9349
+ metadata.route_enabled = task.metadata.route_enabled;
9350
+ }
9351
+ metadata.working_dir = task.working_dir ?? projectPath;
9352
+ }
9353
+ 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;
9354
+ if (taskList) {
9355
+ metadata.task_list_id = taskList.id;
9356
+ metadata.task_list_slug = taskList.slug;
9357
+ metadata.task_list_name = taskList.name;
9358
+ metadata.task_list_project_id = taskList.project_id;
9359
+ metadata.task_list_is_project_default = Boolean(project?.task_list_id && taskList.slug === project.task_list_id);
9360
+ }
9361
+ } catch {}
9362
+ return metadata;
9363
+ }
9364
+ function classifyProjectKind(path) {
9365
+ return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
9366
+ }
9367
+ function isWorktreePath(path) {
9368
+ return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
9369
+ }
9370
+ function inferRootProjectId(project) {
9371
+ return isWorktreePath(project.path) ? null : project.id;
9372
+ }
9373
+ function readMachineLocalPath(project) {
9374
+ const machineId = process.env["TODOS_MACHINE_ID"];
9375
+ if (!machineId)
9376
+ return null;
9377
+ try {
9378
+ const row = getDatabase().query("SELECT path FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(project.id, machineId);
9379
+ return row?.path ?? null;
9380
+ } catch {
9381
+ return null;
9382
+ }
9383
+ }
9194
9384
  async function emitSharedTaskEvent(input) {
9195
9385
  const data = taskEventData(input.task, input.data);
9196
9386
  await new EventsClient().emit({
@@ -9201,12 +9391,7 @@ async function emitSharedTaskEvent(input) {
9201
9391
  message: input.message ?? `${input.type}: ${input.task.title}`,
9202
9392
  data,
9203
9393
  dedupeKey: input.dedupeKey ?? `${input.type}:${input.task.id}:${input.task.version}`,
9204
- metadata: {
9205
- package: "@hasna/todos",
9206
- task_id: input.task.id,
9207
- project_id: input.task.project_id,
9208
- task_list_id: input.task.task_list_id
9209
- }
9394
+ metadata: taskEventMetadata(input.task)
9210
9395
  }, { deliver: true, dedupe: true });
9211
9396
  }
9212
9397
  function emitSharedTaskEventQuiet(input) {
@@ -9217,6 +9402,9 @@ function emitSharedTaskEventQuiet(input) {
9217
9402
  var SOURCE = "todos";
9218
9403
  var init_shared_events = __esm(() => {
9219
9404
  init_dist();
9405
+ init_database();
9406
+ init_projects();
9407
+ init_task_lists();
9220
9408
  });
9221
9409
 
9222
9410
  // src/lib/secret-redaction.ts
@@ -14171,90 +14359,6 @@ var init_dispatch = __esm(() => {
14171
14359
  init_tmux();
14172
14360
  });
14173
14361
 
14174
- // src/db/task-lists.ts
14175
- function rowToTaskList(row) {
14176
- return {
14177
- ...row,
14178
- metadata: JSON.parse(row.metadata || "{}")
14179
- };
14180
- }
14181
- function createTaskList(input, db) {
14182
- const d = db || getDatabase();
14183
- const id = uuid();
14184
- const timestamp = now();
14185
- const slug = input.slug || slugify(input.name);
14186
- if (!input.project_id) {
14187
- const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
14188
- if (existing) {
14189
- throw new Error(`Standalone task list with slug "${slug}" already exists`);
14190
- }
14191
- }
14192
- d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
14193
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
14194
- return getTaskList(id, d);
14195
- }
14196
- function getTaskList(id, db) {
14197
- const d = db || getDatabase();
14198
- const row = d.query("SELECT * FROM task_lists WHERE id = ?").get(id);
14199
- return row ? rowToTaskList(row) : null;
14200
- }
14201
- function getTaskListBySlug(slug, projectId, db) {
14202
- const d = db || getDatabase();
14203
- let row;
14204
- if (projectId) {
14205
- row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id = ?").get(slug, projectId);
14206
- } else {
14207
- row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id IS NULL").get(slug);
14208
- }
14209
- return row ? rowToTaskList(row) : null;
14210
- }
14211
- function listTaskLists(projectId, db) {
14212
- const d = db || getDatabase();
14213
- if (projectId) {
14214
- return d.query("SELECT * FROM task_lists WHERE project_id = ? ORDER BY name").all(projectId).map(rowToTaskList);
14215
- }
14216
- return d.query("SELECT * FROM task_lists ORDER BY name").all().map(rowToTaskList);
14217
- }
14218
- function updateTaskList(id, input, db) {
14219
- const d = db || getDatabase();
14220
- const existing = getTaskList(id, d);
14221
- if (!existing)
14222
- throw new TaskListNotFoundError(id);
14223
- const sets = ["updated_at = ?"];
14224
- const params = [now()];
14225
- if (input.name !== undefined) {
14226
- sets.push("name = ?");
14227
- params.push(input.name);
14228
- }
14229
- if (input.description !== undefined) {
14230
- sets.push("description = ?");
14231
- params.push(input.description);
14232
- }
14233
- if (input.metadata !== undefined) {
14234
- sets.push("metadata = ?");
14235
- params.push(JSON.stringify(input.metadata));
14236
- }
14237
- params.push(id);
14238
- d.run(`UPDATE task_lists SET ${sets.join(", ")} WHERE id = ?`, params);
14239
- return getTaskList(id, d);
14240
- }
14241
- function deleteTaskList(id, db) {
14242
- const d = db || getDatabase();
14243
- return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
14244
- }
14245
- function ensureTaskList(name, slug, projectId, db) {
14246
- const d = db || getDatabase();
14247
- const existing = getTaskListBySlug(slug, projectId, d);
14248
- if (existing)
14249
- return existing;
14250
- return createTaskList({ name, slug, project_id: projectId }, d);
14251
- }
14252
- var init_task_lists = __esm(() => {
14253
- init_types();
14254
- init_database();
14255
- init_projects();
14256
- });
14257
-
14258
14362
  // src/mcp/tools/dispatch.ts
14259
14363
  function registerDispatchTools(server, { shouldRegisterTool, resolveId, formatError }) {
14260
14364
  if (shouldRegisterTool("dispatch_tasks")) {
package/dist/registry.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),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "packageName": "@hasna/todos",
3
- "packageVersion": "0.11.57",
3
+ "packageVersion": "0.11.58",
4
4
  "repository": "https://github.com/hasna/todos.git",
5
- "gitCommit": "eee0cbb94b9542cde66ed520d0a2973886f1d25b",
6
- "generatedAt": "2026-06-26T15:55:00.866Z"
5
+ "gitCommit": "5f39ea1289e91b8985c1b17e3a21cc58565c0eeb",
6
+ "generatedAt": "2026-06-26T17:33:40.342Z"
7
7
  }