@hasna/todos 0.15.41 → 0.15.46

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/index.js CHANGED
@@ -8651,7 +8651,7 @@ function getTaskGraph(taskId, direction = "both", db) {
8651
8651
  const deps = getTaskDependencies(t.id, d);
8652
8652
  const hasUnfinishedDeps = deps.some((dep) => {
8653
8653
  const depTask = getTask(dep.depends_on, d);
8654
- return depTask && depTask.status !== "completed";
8654
+ return depTask && isBlockingDependencyStatus(depTask.status);
8655
8655
  });
8656
8656
  return { id: t.id, short_id: t.short_id, title: t.title, status: t.status, priority: t.priority, is_blocked: hasUnfinishedDeps };
8657
8657
  }
@@ -8911,7 +8911,7 @@ function getBlockingDeps(id, db) {
8911
8911
  const blocking = [];
8912
8912
  for (const dep of deps) {
8913
8913
  const task = getTask(dep.depends_on, d);
8914
- if (task && task.status !== "completed")
8914
+ if (task && isBlockingDependencyStatus(task.status))
8915
8915
  blocking.push(task);
8916
8916
  }
8917
8917
  return blocking;
@@ -9235,7 +9235,7 @@ function getNextTask(agentId, filters, db) {
9235
9235
  conditions.push(`id IN (SELECT task_id FROM task_tags WHERE tag IN (${placeholders}))`);
9236
9236
  params.push(...filters.tags);
9237
9237
  }
9238
- conditions.push("id NOT IN (SELECT td.task_id FROM task_dependencies td JOIN tasks dep ON dep.id = td.depends_on WHERE dep.status != 'completed')");
9238
+ conditions.push("id NOT IN (SELECT td.task_id FROM task_dependencies td JOIN tasks dep ON dep.id = td.depends_on WHERE dep.status NOT IN ('completed', 'cancelled'))");
9239
9239
  const where = conditions.join(" AND ");
9240
9240
  let recentProjectIds = [];
9241
9241
  const assignedAliasParams = [];
@@ -9279,7 +9279,7 @@ function getActiveWork(filters, db) {
9279
9279
  }
9280
9280
  function getTasksChangedSince(since, filters, db) {
9281
9281
  const d = db || getDatabase();
9282
- const conditions = ["updated_at > ?"];
9282
+ const conditions = ["(julianday(updated_at) IS NULL OR julianday(updated_at) > julianday(?))"];
9283
9283
  const params = [since];
9284
9284
  if (filters?.project_id) {
9285
9285
  conditions.push("project_id = ?");
@@ -12922,7 +12922,7 @@ var init_dispatches = __esm(() => {
12922
12922
  // package.json
12923
12923
  var package_default = {
12924
12924
  name: "@hasna/todos",
12925
- version: "0.15.41",
12925
+ version: "0.15.46",
12926
12926
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
12927
12927
  type: "module",
12928
12928
  main: "dist/index.js",
@@ -13040,7 +13040,7 @@ var package_default = {
13040
13040
  author: "Andrei Hasna <andrei@hasna.com>",
13041
13041
  license: "Apache-2.0",
13042
13042
  dependencies: {
13043
- "@hasna/contracts": "0.13.3",
13043
+ "@hasna/contracts": "0.13.4",
13044
13044
  "@hasna/events": "^0.1.11",
13045
13045
  "@modelcontextprotocol/sdk": "^1.12.1",
13046
13046
  chalk: "^5.4.1",
@@ -27150,7 +27150,7 @@ function matchesExtraFilters(task2, filter) {
27150
27150
  }
27151
27151
  if (filter.tags?.length) {
27152
27152
  const taskTags2 = new Set(task2.tags ?? []);
27153
- if (!filter.tags.every((tag) => taskTags2.has(tag)))
27153
+ if (!filter.tags.some((tag) => taskTags2.has(tag)))
27154
27154
  return false;
27155
27155
  }
27156
27156
  if (filter.include_subtasks !== true && filter.parent_id === undefined && task2.parent_id)
@@ -27314,6 +27314,59 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
27314
27314
  init_types();
27315
27315
  import { randomUUID as randomUUID3 } from "crypto";
27316
27316
  init_creator_identity();
27317
+
27318
+ // src/lib/instant-compare.ts
27319
+ var SQLITE_STAMP = /^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)?(?:Z|([+-])(\d{2}):(\d{2}))?)?$/;
27320
+ var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
27321
+ function isLeapYear(year) {
27322
+ return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
27323
+ }
27324
+ function sqliteJulianDay(value) {
27325
+ const m = SQLITE_STAMP.exec(value);
27326
+ if (!m)
27327
+ return null;
27328
+ const year = Number(m[1]);
27329
+ const month = Number(m[2]);
27330
+ const day = Number(m[3]);
27331
+ const hour = m[4] === undefined ? 0 : Number(m[4]);
27332
+ const minute = m[5] === undefined ? 0 : Number(m[5]);
27333
+ const second = m[6] === undefined ? 0 : Number(m[6]);
27334
+ const frac = m[7];
27335
+ const sign = m[8];
27336
+ const offsetHour = m[9];
27337
+ const offsetMinute = m[10];
27338
+ if (month < 1 || month > 12)
27339
+ return null;
27340
+ if (hour > 23 || minute > 59 || second > 59)
27341
+ return null;
27342
+ const maxDay = (DAYS_IN_MONTH[month - 1] ?? 0) + (month === 2 && isLeapYear(year) ? 1 : 0);
27343
+ if (day < 1 || day > maxDay)
27344
+ return null;
27345
+ let offsetMinutes = 0;
27346
+ if (sign !== undefined) {
27347
+ const oh = Number(offsetHour ?? "0");
27348
+ const om = Number(offsetMinute ?? "0");
27349
+ if (oh > 23 || om > 59)
27350
+ return null;
27351
+ offsetMinutes = oh * 60 + om;
27352
+ if (sign === "-")
27353
+ offsetMinutes = -offsetMinutes;
27354
+ }
27355
+ const micros = frac === undefined ? 0 : Number((frac + "000000").slice(0, 6));
27356
+ const ms = Date.UTC(year, month - 1, day, hour, minute, second);
27357
+ return (ms + micros / 1000) / 86400000 + 2440587.5 - offsetMinutes / 1440;
27358
+ }
27359
+ function changedSinceStampNewer(stamp, since) {
27360
+ const stampJd = sqliteJulianDay(stamp);
27361
+ if (stampJd === null)
27362
+ return true;
27363
+ const sinceJd = sqliteJulianDay(since);
27364
+ if (sinceJd === null)
27365
+ return false;
27366
+ return stampJd > sinceJd;
27367
+ }
27368
+
27369
+ // src/storage/postgres-adapter.ts
27317
27370
  init_stale_lock_handoff();
27318
27371
 
27319
27372
  // src/storage/postgres-sync.ts
@@ -27928,11 +27981,16 @@ class PostgresJsonRecordStore {
27928
27981
  return context?.requestId ?? this.sourceMachineId ?? null;
27929
27982
  }
27930
27983
  async ensureSchema() {
27931
- this.schemaReady ??= (async () => {
27932
- for (const sql of postgresTodosSyncSchemaSql(this.tableName, this.cursorTableName)) {
27933
- await this.options.client.query(sql);
27934
- }
27935
- })();
27984
+ if (!this.schemaReady) {
27985
+ this.schemaReady = (async () => {
27986
+ for (const sql of postgresTodosSyncSchemaSql(this.tableName, this.cursorTableName)) {
27987
+ await this.options.client.query(sql);
27988
+ }
27989
+ })().catch((error) => {
27990
+ this.schemaReady = null;
27991
+ throw error;
27992
+ });
27993
+ }
27936
27994
  await this.schemaReady;
27937
27995
  }
27938
27996
  async get(type, id) {
@@ -29697,7 +29755,7 @@ async function getActiveWork2(filters, store) {
29697
29755
  }));
29698
29756
  }
29699
29757
  async function getChangedSince(since, filters, store) {
29700
- return (await listTasks2(filters ?? {}, store)).filter((task2) => task2.updated_at > since);
29758
+ return (await listTasks2(filters ?? {}, store)).filter((task2) => changedSinceStampNewer(task2.updated_at ?? "", since));
29701
29759
  }
29702
29760
  async function createProject2(input, store, context) {
29703
29761
  const timestamp3 = new Date().toISOString();
@@ -35052,14 +35110,23 @@ class PostgresTodosProjectRegistrationBackend {
35052
35110
  this.cursorTableName = safeIdentifier(options.cursorTableName ?? "todos_sync_cursors", "cursorTableName");
35053
35111
  }
35054
35112
  async ensureSchema() {
35055
- this.schemaReady ??= (async () => {
35056
- for (const statement of postgresTodosSyncSchemaSql(this.tableName, this.cursorTableName)) {
35057
- await this.client.query(statement);
35058
- }
35059
- for (const statement of postgresTodosProjectRegistrationSchemaSql()) {
35060
- await this.client.query(statement);
35113
+ if (this.schemaReady === null) {
35114
+ const attempt = (async () => {
35115
+ for (const statement of postgresTodosSyncSchemaSql(this.tableName, this.cursorTableName)) {
35116
+ await this.client.query(statement);
35117
+ }
35118
+ for (const statement of postgresTodosProjectRegistrationSchemaSql()) {
35119
+ await this.client.query(statement);
35120
+ }
35121
+ })();
35122
+ this.schemaReady = attempt;
35123
+ try {
35124
+ await attempt;
35125
+ } catch (error) {
35126
+ this.schemaReady = null;
35127
+ throw error;
35061
35128
  }
35062
- })();
35129
+ }
35063
35130
  await this.schemaReady;
35064
35131
  }
35065
35132
  async transaction(fn) {
@@ -42717,12 +42784,21 @@ class PostgresTodosTaskManifestBackend {
42717
42784
  this.tenantId = options.tenantId ?? "default";
42718
42785
  }
42719
42786
  async ensureSchema() {
42720
- this.schemaReady ??= (async () => {
42721
- for (const sql of postgresTodosSyncSchemaSql(this.tableName))
42722
- await this.client.query(sql);
42723
- for (const sql of postgresTodosTaskManifestSchemaSql(this.tenantId))
42724
- await this.client.query(sql);
42725
- })();
42787
+ if (this.schemaReady === null) {
42788
+ const attempt = (async () => {
42789
+ for (const sql of postgresTodosSyncSchemaSql(this.tableName))
42790
+ await this.client.query(sql);
42791
+ for (const sql of postgresTodosTaskManifestSchemaSql(this.tenantId))
42792
+ await this.client.query(sql);
42793
+ })();
42794
+ this.schemaReady = attempt;
42795
+ try {
42796
+ await attempt;
42797
+ } catch (error) {
42798
+ this.schemaReady = null;
42799
+ throw error;
42800
+ }
42801
+ }
42726
42802
  await this.schemaReady;
42727
42803
  }
42728
42804
  async insertSync(tx, objectType2, objectId, payload, now4) {
@@ -44072,12 +44148,21 @@ class PostgresTodosTaskSubtreeTransferBackend {
44072
44148
  this.tenantId = options.tenantId ?? "default";
44073
44149
  }
44074
44150
  async ensureSchema() {
44075
- this.schemaReady ??= (async () => {
44076
- for (const sql of postgresTodosSyncSchemaSql(this.tableName))
44077
- await this.client.query(sql);
44078
- for (const sql of postgresTodosTaskSubtreeTransferSchemaSql())
44079
- await this.client.query(sql);
44080
- })();
44151
+ if (this.schemaReady === null) {
44152
+ const attempt = (async () => {
44153
+ for (const sql of postgresTodosSyncSchemaSql(this.tableName))
44154
+ await this.client.query(sql);
44155
+ for (const sql of postgresTodosTaskSubtreeTransferSchemaSql())
44156
+ await this.client.query(sql);
44157
+ })();
44158
+ this.schemaReady = attempt;
44159
+ try {
44160
+ await attempt;
44161
+ } catch (error) {
44162
+ this.schemaReady = null;
44163
+ throw error;
44164
+ }
44165
+ }
44081
44166
  await this.schemaReady;
44082
44167
  }
44083
44168
  async snapshot(client, input, forUpdate = false) {
@@ -48928,7 +49013,7 @@ function scoreHealth(scope, scopeId, db) {
48928
49013
  const blockers = d.query(`SELECT dep.id, dep.short_id, dep.title, dep.status
48929
49014
  FROM task_dependencies td
48930
49015
  JOIN tasks dep ON dep.id = td.depends_on
48931
- WHERE td.task_id = ? AND dep.status != 'completed'`).all(task3.id);
49016
+ WHERE td.task_id = ? AND dep.status NOT IN ('completed', 'cancelled')`).all(task3.id);
48932
49017
  return { id: task3.id, short_id: task3.short_id, title: redactEvidenceText(task3.title), blockers };
48933
49018
  }).filter((entry2) => entry2.blockers.length > 0);
48934
49019
  const overdue = tasks.filter((task3) => activeTaskIds.has(task3.id) && Boolean(task3.due_at && task3.due_at < generatedAt)).map((task3) => ({ id: task3.id, short_id: task3.short_id, title: redactEvidenceText(task3.title), due_at: task3.due_at }));
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Since-cursor comparison for changed-since feeds.
3
+ *
4
+ * Stored `updated_at` stamps mix ISO ("2026-08-05T18:54:55.814Z") with
5
+ * space-form ("2026-06-10 11:24:47" — the DDL default `datetime('now')`, plus
6
+ * snapshot import/sync). As TEXT, space (0x20) sorts before 'T' (0x54), so a
7
+ * raw `updated_at > since` comparison silently excludes space-form rows that
8
+ * are genuinely NEWER than an ISO cursor. This compares the stamps as
9
+ * INSTANTS, mirroring the SQL `julianday()` predicate used by the
10
+ * SQLite/Postgres updated_after paths (task-crud.ts), including its
11
+ * keep-unparseable semantics: a stamp julianday() cannot parse yields NULL
12
+ * and the row is KEPT, because "cannot read the row's timestamp" is not
13
+ * "older than the cursor".
14
+ *
15
+ * The parser is deliberately SQLite-faithful rather than `Date.parse`-based,
16
+ * because the two disagree in ways that change the comparison:
17
+ *
18
+ * - SQLite accepts ONLY an uppercase 'T' (or space) separator and an
19
+ * uppercase 'Z' (or `±HH:MM`) offset; `Date.parse` also accepts lowercase
20
+ * 't'/'z', so a row SQLite would read as NULL (and KEEP) would otherwise be
21
+ * excluded.
22
+ * - SQLite parses the fractional seconds to microsecond precision;
23
+ * `Date.parse` truncates to milliseconds, so stamps that differ only in the
24
+ * sub-millisecond digits would compare equal (and be excluded) instead of
25
+ * newer.
26
+ * - SQLite rejects out-of-range calendar fields (month 13, day 32, hour 25,
27
+ * second 61); `Date.UTC` normalizes them, which would silently accept a
28
+ * stamp SQLite treats as unreadable.
29
+ *
30
+ * Naive (no-offset) stamps are read as UTC, matching SQLite julianday().
31
+ * Offsets are applied by subtraction, matching julianday('...+02:00') ==
32
+ * UTC minus two hours.
33
+ */
34
+ export declare function changedSinceStampNewer(stamp: string, since: string): boolean;
35
+ //# sourceMappingURL=instant-compare.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instant-compare.d.ts","sourceRoot":"","sources":["../../src/lib/instant-compare.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAqDH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAM5E"}
package/dist/mcp/index.js CHANGED
@@ -13289,7 +13289,7 @@ function getTaskGraph(taskId, direction = "both", db) {
13289
13289
  const deps = getTaskDependencies(t.id, d);
13290
13290
  const hasUnfinishedDeps = deps.some((dep) => {
13291
13291
  const depTask = getTask(dep.depends_on, d);
13292
- return depTask && depTask.status !== "completed";
13292
+ return depTask && isBlockingDependencyStatus(depTask.status);
13293
13293
  });
13294
13294
  return { id: t.id, short_id: t.short_id, title: t.title, status: t.status, priority: t.priority, is_blocked: hasUnfinishedDeps };
13295
13295
  }
@@ -13549,7 +13549,7 @@ function getBlockingDeps(id, db) {
13549
13549
  const blocking = [];
13550
13550
  for (const dep of deps) {
13551
13551
  const task = getTask(dep.depends_on, d);
13552
- if (task && task.status !== "completed")
13552
+ if (task && isBlockingDependencyStatus(task.status))
13553
13553
  blocking.push(task);
13554
13554
  }
13555
13555
  return blocking;
@@ -13873,7 +13873,7 @@ function getNextTask(agentId, filters, db) {
13873
13873
  conditions.push(`id IN (SELECT task_id FROM task_tags WHERE tag IN (${placeholders}))`);
13874
13874
  params.push(...filters.tags);
13875
13875
  }
13876
- conditions.push("id NOT IN (SELECT td.task_id FROM task_dependencies td JOIN tasks dep ON dep.id = td.depends_on WHERE dep.status != 'completed')");
13876
+ conditions.push("id NOT IN (SELECT td.task_id FROM task_dependencies td JOIN tasks dep ON dep.id = td.depends_on WHERE dep.status NOT IN ('completed', 'cancelled'))");
13877
13877
  const where = conditions.join(" AND ");
13878
13878
  let recentProjectIds = [];
13879
13879
  const assignedAliasParams = [];
@@ -13917,7 +13917,7 @@ function getActiveWork(filters, db) {
13917
13917
  }
13918
13918
  function getTasksChangedSince(since, filters, db) {
13919
13919
  const d = db || getDatabase();
13920
- const conditions = ["updated_at > ?"];
13920
+ const conditions = ["(julianday(updated_at) IS NULL OR julianday(updated_at) > julianday(?))"];
13921
13921
  const params = [since];
13922
13922
  if (filters?.project_id) {
13923
13923
  conditions.push("project_id = ?");
@@ -18867,6 +18867,60 @@ var init_assignee_context = __esm(() => {
18867
18867
  init_assignee_validation();
18868
18868
  });
18869
18869
 
18870
+ // src/lib/instant-compare.ts
18871
+ function isLeapYear(year) {
18872
+ return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
18873
+ }
18874
+ function sqliteJulianDay(value) {
18875
+ const m = SQLITE_STAMP.exec(value);
18876
+ if (!m)
18877
+ return null;
18878
+ const year = Number(m[1]);
18879
+ const month = Number(m[2]);
18880
+ const day = Number(m[3]);
18881
+ const hour = m[4] === undefined ? 0 : Number(m[4]);
18882
+ const minute = m[5] === undefined ? 0 : Number(m[5]);
18883
+ const second = m[6] === undefined ? 0 : Number(m[6]);
18884
+ const frac = m[7];
18885
+ const sign = m[8];
18886
+ const offsetHour = m[9];
18887
+ const offsetMinute = m[10];
18888
+ if (month < 1 || month > 12)
18889
+ return null;
18890
+ if (hour > 23 || minute > 59 || second > 59)
18891
+ return null;
18892
+ const maxDay = (DAYS_IN_MONTH[month - 1] ?? 0) + (month === 2 && isLeapYear(year) ? 1 : 0);
18893
+ if (day < 1 || day > maxDay)
18894
+ return null;
18895
+ let offsetMinutes = 0;
18896
+ if (sign !== undefined) {
18897
+ const oh = Number(offsetHour ?? "0");
18898
+ const om = Number(offsetMinute ?? "0");
18899
+ if (oh > 23 || om > 59)
18900
+ return null;
18901
+ offsetMinutes = oh * 60 + om;
18902
+ if (sign === "-")
18903
+ offsetMinutes = -offsetMinutes;
18904
+ }
18905
+ const micros = frac === undefined ? 0 : Number((frac + "000000").slice(0, 6));
18906
+ const ms = Date.UTC(year, month - 1, day, hour, minute, second);
18907
+ return (ms + micros / 1000) / 86400000 + 2440587.5 - offsetMinutes / 1440;
18908
+ }
18909
+ function changedSinceStampNewer(stamp, since) {
18910
+ const stampJd = sqliteJulianDay(stamp);
18911
+ if (stampJd === null)
18912
+ return true;
18913
+ const sinceJd = sqliteJulianDay(since);
18914
+ if (sinceJd === null)
18915
+ return false;
18916
+ return stampJd > sinceJd;
18917
+ }
18918
+ var SQLITE_STAMP, DAYS_IN_MONTH;
18919
+ var init_instant_compare = __esm(() => {
18920
+ SQLITE_STAMP = /^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)?(?:Z|([+-])(\d{2}):(\d{2}))?)?$/;
18921
+ DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
18922
+ });
18923
+
18870
18924
  // src/lib/plan-project-link-contract.ts
18871
18925
  import { createHash as createHash3 } from "crypto";
18872
18926
  function canonicalPlanProjectLinkJson(value) {
@@ -21920,6 +21974,7 @@ var UUID_RE, completionCapabilityCache, retryCapabilityCache, taskCreatorCapabil
21920
21974
  var init_cloud_router = __esm(() => {
21921
21975
  init_types();
21922
21976
  init_redaction();
21977
+ init_instant_compare();
21923
21978
  init_plan_project_link_contract();
21924
21979
  init_http_client();
21925
21980
  init_adoption_validation();
@@ -34724,7 +34779,7 @@ function scoreHealth(scope, scopeId, db) {
34724
34779
  const blockers = d.query(`SELECT dep.id, dep.short_id, dep.title, dep.status
34725
34780
  FROM task_dependencies td
34726
34781
  JOIN tasks dep ON dep.id = td.depends_on
34727
- WHERE td.task_id = ? AND dep.status != 'completed'`).all(task.id);
34782
+ WHERE td.task_id = ? AND dep.status NOT IN ('completed', 'cancelled')`).all(task.id);
34728
34783
  return { id: task.id, short_id: task.short_id, title: redactEvidenceText(task.title), blockers };
34729
34784
  }).filter((entry) => entry.blockers.length > 0);
34730
34785
  const overdue = tasks.filter((task) => activeTaskIds.has(task.id) && Boolean(task.due_at && task.due_at < generatedAt)).map((task) => ({ id: task.id, short_id: task.short_id, title: redactEvidenceText(task.title), due_at: task.due_at }));
@@ -36047,7 +36102,7 @@ var package_default;
36047
36102
  var init_package = __esm(() => {
36048
36103
  package_default = {
36049
36104
  name: "@hasna/todos",
36050
- version: "0.15.41",
36105
+ version: "0.15.46",
36051
36106
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
36052
36107
  type: "module",
36053
36108
  main: "dist/index.js",
@@ -36165,7 +36220,7 @@ var init_package = __esm(() => {
36165
36220
  author: "Andrei Hasna <andrei@hasna.com>",
36166
36221
  license: "Apache-2.0",
36167
36222
  dependencies: {
36168
- "@hasna/contracts": "0.13.3",
36223
+ "@hasna/contracts": "0.13.4",
36169
36224
  "@hasna/events": "^0.1.11",
36170
36225
  "@modelcontextprotocol/sdk": "^1.12.1",
36171
36226
  chalk: "^5.4.1",
@@ -46241,7 +46296,7 @@ function matchesExtraFilters(task2, filter) {
46241
46296
  }
46242
46297
  if (filter.tags?.length) {
46243
46298
  const taskTags = new Set(task2.tags ?? []);
46244
- if (!filter.tags.every((tag) => taskTags.has(tag)))
46299
+ if (!filter.tags.some((tag) => taskTags.has(tag)))
46245
46300
  return false;
46246
46301
  }
46247
46302
  if (filter.include_subtasks !== true && filter.parent_id === undefined && task2.parent_id)
@@ -47433,11 +47488,16 @@ class PostgresJsonRecordStore {
47433
47488
  return context?.requestId ?? this.sourceMachineId ?? null;
47434
47489
  }
47435
47490
  async ensureSchema() {
47436
- this.schemaReady ??= (async () => {
47437
- for (const sql of postgresTodosSyncSchemaSql(this.tableName, this.cursorTableName)) {
47438
- await this.options.client.query(sql);
47439
- }
47440
- })();
47491
+ if (!this.schemaReady) {
47492
+ this.schemaReady = (async () => {
47493
+ for (const sql of postgresTodosSyncSchemaSql(this.tableName, this.cursorTableName)) {
47494
+ await this.options.client.query(sql);
47495
+ }
47496
+ })().catch((error) => {
47497
+ this.schemaReady = null;
47498
+ throw error;
47499
+ });
47500
+ }
47441
47501
  await this.schemaReady;
47442
47502
  }
47443
47503
  async get(type, id) {
@@ -49199,7 +49259,7 @@ async function getActiveWork2(filters, store) {
49199
49259
  }));
49200
49260
  }
49201
49261
  async function getChangedSince(since, filters, store) {
49202
- return (await listTasks3(filters ?? {}, store)).filter((task2) => task2.updated_at > since);
49262
+ return (await listTasks3(filters ?? {}, store)).filter((task2) => changedSinceStampNewer(task2.updated_at ?? "", since));
49203
49263
  }
49204
49264
  async function createProject2(input, store, context) {
49205
49265
  const timestamp4 = new Date().toISOString();
@@ -49758,6 +49818,7 @@ var CLOUD_LOCK_EXPIRY_MINUTES = 30, TASK_ORDER_TIEBREAK = "CASE payload->>'prior
49758
49818
  var init_postgres_adapter = __esm(() => {
49759
49819
  init_types();
49760
49820
  init_creator_identity();
49821
+ init_instant_compare();
49761
49822
  init_plan_project_link_contract();
49762
49823
  init_stale_lock_handoff();
49763
49824
  init_postgres_sync();
@@ -50188,10 +50249,19 @@ class PostgresPrGroupLedgerPersistence {
50188
50249
  this.client = client;
50189
50250
  }
50190
50251
  async ensureSchema() {
50191
- this.schemaReady ??= (async () => {
50192
- for (const statement of postgresPrGroupSchemaSql())
50193
- await this.client.query(statement);
50194
- })();
50252
+ if (this.schemaReady === null) {
50253
+ const attempt = (async () => {
50254
+ for (const statement of postgresPrGroupSchemaSql())
50255
+ await this.client.query(statement);
50256
+ })();
50257
+ this.schemaReady = attempt;
50258
+ try {
50259
+ await attempt;
50260
+ } catch (error) {
50261
+ this.schemaReady = null;
50262
+ throw error;
50263
+ }
50264
+ }
50195
50265
  return this.schemaReady;
50196
50266
  }
50197
50267
  async transaction(fn) {
@@ -50609,14 +50679,23 @@ class PostgresTodosProjectRegistrationBackend {
50609
50679
  this.cursorTableName = safeIdentifier(options.cursorTableName ?? "todos_sync_cursors", "cursorTableName");
50610
50680
  }
50611
50681
  async ensureSchema() {
50612
- this.schemaReady ??= (async () => {
50613
- for (const statement of postgresTodosSyncSchemaSql(this.tableName, this.cursorTableName)) {
50614
- await this.client.query(statement);
50615
- }
50616
- for (const statement of postgresTodosProjectRegistrationSchemaSql()) {
50617
- await this.client.query(statement);
50682
+ if (this.schemaReady === null) {
50683
+ const attempt = (async () => {
50684
+ for (const statement of postgresTodosSyncSchemaSql(this.tableName, this.cursorTableName)) {
50685
+ await this.client.query(statement);
50686
+ }
50687
+ for (const statement of postgresTodosProjectRegistrationSchemaSql()) {
50688
+ await this.client.query(statement);
50689
+ }
50690
+ })();
50691
+ this.schemaReady = attempt;
50692
+ try {
50693
+ await attempt;
50694
+ } catch (error) {
50695
+ this.schemaReady = null;
50696
+ throw error;
50618
50697
  }
50619
- })();
50698
+ }
50620
50699
  await this.schemaReady;
50621
50700
  }
50622
50701
  async transaction(fn) {
@@ -53326,12 +53405,21 @@ class PostgresTodosTaskManifestBackend {
53326
53405
  this.tenantId = options.tenantId ?? "default";
53327
53406
  }
53328
53407
  async ensureSchema() {
53329
- this.schemaReady ??= (async () => {
53330
- for (const sql of postgresTodosSyncSchemaSql(this.tableName))
53331
- await this.client.query(sql);
53332
- for (const sql of postgresTodosTaskManifestSchemaSql(this.tenantId))
53333
- await this.client.query(sql);
53334
- })();
53408
+ if (this.schemaReady === null) {
53409
+ const attempt = (async () => {
53410
+ for (const sql of postgresTodosSyncSchemaSql(this.tableName))
53411
+ await this.client.query(sql);
53412
+ for (const sql of postgresTodosTaskManifestSchemaSql(this.tenantId))
53413
+ await this.client.query(sql);
53414
+ })();
53415
+ this.schemaReady = attempt;
53416
+ try {
53417
+ await attempt;
53418
+ } catch (error) {
53419
+ this.schemaReady = null;
53420
+ throw error;
53421
+ }
53422
+ }
53335
53423
  await this.schemaReady;
53336
53424
  }
53337
53425
  async insertSync(tx, objectType2, objectId, payload, now4) {
@@ -54631,12 +54719,21 @@ class PostgresTodosTaskSubtreeTransferBackend {
54631
54719
  this.tenantId = options.tenantId ?? "default";
54632
54720
  }
54633
54721
  async ensureSchema() {
54634
- this.schemaReady ??= (async () => {
54635
- for (const sql of postgresTodosSyncSchemaSql(this.tableName))
54636
- await this.client.query(sql);
54637
- for (const sql of postgresTodosTaskSubtreeTransferSchemaSql())
54638
- await this.client.query(sql);
54639
- })();
54722
+ if (this.schemaReady === null) {
54723
+ const attempt = (async () => {
54724
+ for (const sql of postgresTodosSyncSchemaSql(this.tableName))
54725
+ await this.client.query(sql);
54726
+ for (const sql of postgresTodosTaskSubtreeTransferSchemaSql())
54727
+ await this.client.query(sql);
54728
+ })();
54729
+ this.schemaReady = attempt;
54730
+ try {
54731
+ await attempt;
54732
+ } catch (error) {
54733
+ this.schemaReady = null;
54734
+ throw error;
54735
+ }
54736
+ }
54640
54737
  await this.schemaReady;
54641
54738
  }
54642
54739
  async snapshot(client, input, forUpdate = false) {
@@ -55723,7 +55820,10 @@ async function ensureCloudSchema() {
55723
55820
  await client.query(sql);
55724
55821
  }
55725
55822
  await getApiKeyStore().ensureSchema();
55726
- })();
55823
+ })().catch((error) => {
55824
+ schemaEnsured = null;
55825
+ throw error;
55826
+ });
55727
55827
  return schemaEnsured;
55728
55828
  }
55729
55829
  async function ensureCloudCommentCursorIndex() {
package/dist/mcp.js CHANGED
@@ -41,7 +41,7 @@ var __require = import.meta.require;
41
41
  // package.json
42
42
  var package_default = {
43
43
  name: "@hasna/todos",
44
- version: "0.15.41",
44
+ version: "0.15.46",
45
45
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
46
46
  type: "module",
47
47
  main: "dist/index.js",
@@ -159,7 +159,7 @@ var package_default = {
159
159
  author: "Andrei Hasna <andrei@hasna.com>",
160
160
  license: "Apache-2.0",
161
161
  dependencies: {
162
- "@hasna/contracts": "0.13.3",
162
+ "@hasna/contracts": "0.13.4",
163
163
  "@hasna/events": "^0.1.11",
164
164
  "@modelcontextprotocol/sdk": "^1.12.1",
165
165
  chalk: "^5.4.1",
@@ -1 +1 @@
1
- {"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../src/pr-groups/postgres.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,EAC7B,KAAK,wBAAwB,EAC7B,KAAK,aAAa,EACnB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,0BAA2B,SAAQ,wBAAwB;IAC1E,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,EAAE,wBAAwB,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAClF;AAED,wBAAgB,wBAAwB,IAAI,MAAM,EAAE,CAyInD;AAgPD,qBAAa,gCAAiC,YAAW,wBAAwB;IAInE,OAAO,CAAC,QAAQ,CAAC,MAAM;IAHnC,QAAQ,CAAC,SAAS,EAAG,QAAQ,CAAU;IACvC,OAAO,CAAC,WAAW,CAA8B;gBAEpB,MAAM,EAAE,0BAA0B;IAEzD,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAO7B,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,wBAAwB,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IA2B5E,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAKnD,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAK9D,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,uBAAuB,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAK7F,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAYhF,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAS7C,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;CAQ1E"}
1
+ {"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../src/pr-groups/postgres.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,EAC7B,KAAK,wBAAwB,EAC7B,KAAK,aAAa,EACnB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,0BAA2B,SAAQ,wBAAwB;IAC1E,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,EAAE,wBAAwB,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAClF;AAED,wBAAgB,wBAAwB,IAAI,MAAM,EAAE,CAyInD;AAgPD,qBAAa,gCAAiC,YAAW,wBAAwB;IAInE,OAAO,CAAC,QAAQ,CAAC,MAAM;IAHnC,QAAQ,CAAC,SAAS,EAAG,QAAQ,CAAU;IACvC,OAAO,CAAC,WAAW,CAA8B;gBAEpB,MAAM,EAAE,0BAA0B;IAEzD,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAoB7B,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,wBAAwB,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IA2B5E,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAKnD,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAK9D,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,uBAAuB,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAK7F,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAYhF,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAS7C,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;CAQ1E"}
@@ -1 +1 @@
1
- {"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../src/project-registration/postgres.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,wBAAwB,EAC9B,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EACV,+BAA+B,EAC/B,0CAA0C,EAC1C,sCAAsC,EACtC,kCAAkC,EAClC,oCAAoC,EACpC,kCAAkC,EAElC,6BAA6B,EAC7B,0BAA0B,EAC3B,MAAM,cAAc,CAAC;AAEtB,OAAO,EAEL,KAAK,oCAAoC,EAC1C,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,sCACjB,SAAQ,wBAAwB;IAC9B,WAAW,CAAC,CAAC,EACX,EAAE,EAAE,CAAC,MAAM,EAAE,wBAAwB,KAAK,OAAO,CAAC,CAAC,CAAC,GACnD,OAAO,CAAC,CAAC,CAAC,CAAC;CACf;AAED,MAAM,WAAW,8CAA8C;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAuaD,qBAAa,uCACb,YAAW,+BAA+B;IAQtC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAPzB,QAAQ,CAAC,IAAI,EAAG,YAAY,CAAU;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,WAAW,CAA8B;gBAG9B,MAAM,EAAE,sCAAsC,EAC/D,OAAO,GAAE,8CAAmD;IAUxD,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAe7B,WAAW,CAAC,CAAC,EACjB,EAAE,EAAE,CAAC,WAAW,EAAE,0CAA0C,KAAK,OAAO,CAAC,CAAC,CAAC,GAC1E,OAAO,CAAC,CAAC,CAAC;YAkBC,MAAM;IAUd,mBAAmB,CACvB,QAAQ,EAAE,oCAAoC,GAC7C,OAAO,CAAC,kCAAkC,GAAG,IAAI,CAAC;IAI/C,cAAc,CAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,kCAAkC,GAAG,IAAI,CAAC;IAI/C,UAAU,CACd,KAAK,EAAE,sCAAsC,EAC7C,YAAY,EAAE,oCAAoC,EAClD,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,kCAAkC,GAAG,IAAI,CAAC;IAI/C,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAI/C,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAIjD,oCAAoC,CAAC,KAAK,EAAE;QAChD,gBAAgB,EAAE,MAAM,CAAC;QACzB,YAAY,EAAE,MAAM,CAAC;QACrB,eAAe,EAAE,OAAO,CAAC;KAC1B,GAAG,OAAO,CAAC,MAAM,CAAC;IA8Cb,6BAA6B,CAAC,KAAK,EAAE;QACzC,gBAAgB,EAAE,MAAM,CAAC;QACzB,YAAY,EAAE,MAAM,CAAC;QACrB,eAAe,EAAE,OAAO,CAAC;QACzB,KAAK,EAAE,0BAA0B,GAAG,IAAI,CAAC;QACzC,KAAK,EAAE,MAAM,CAAC;KACf,GAAG,OAAO,CAAC,6BAA6B,EAAE,CAAC;CAgD7C"}
1
+ {"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../src/project-registration/postgres.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,wBAAwB,EAC9B,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EACV,+BAA+B,EAC/B,0CAA0C,EAC1C,sCAAsC,EACtC,kCAAkC,EAClC,oCAAoC,EACpC,kCAAkC,EAElC,6BAA6B,EAC7B,0BAA0B,EAC3B,MAAM,cAAc,CAAC;AAEtB,OAAO,EAEL,KAAK,oCAAoC,EAC1C,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,sCACjB,SAAQ,wBAAwB;IAC9B,WAAW,CAAC,CAAC,EACX,EAAE,EAAE,CAAC,MAAM,EAAE,wBAAwB,KAAK,OAAO,CAAC,CAAC,CAAC,GACnD,OAAO,CAAC,CAAC,CAAC,CAAC;CACf;AAED,MAAM,WAAW,8CAA8C;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAuaD,qBAAa,uCACb,YAAW,+BAA+B;IAQtC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAPzB,QAAQ,CAAC,IAAI,EAAG,YAAY,CAAU;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,WAAW,CAA8B;gBAG9B,MAAM,EAAE,sCAAsC,EAC/D,OAAO,GAAE,8CAAmD;IAUxD,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IA4B7B,WAAW,CAAC,CAAC,EACjB,EAAE,EAAE,CAAC,WAAW,EAAE,0CAA0C,KAAK,OAAO,CAAC,CAAC,CAAC,GAC1E,OAAO,CAAC,CAAC,CAAC;YAkBC,MAAM;IAUd,mBAAmB,CACvB,QAAQ,EAAE,oCAAoC,GAC7C,OAAO,CAAC,kCAAkC,GAAG,IAAI,CAAC;IAI/C,cAAc,CAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,kCAAkC,GAAG,IAAI,CAAC;IAI/C,UAAU,CACd,KAAK,EAAE,sCAAsC,EAC7C,YAAY,EAAE,oCAAoC,EAClD,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,kCAAkC,GAAG,IAAI,CAAC;IAI/C,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAI/C,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAIjD,oCAAoC,CAAC,KAAK,EAAE;QAChD,gBAAgB,EAAE,MAAM,CAAC;QACzB,YAAY,EAAE,MAAM,CAAC;QACrB,eAAe,EAAE,OAAO,CAAC;KAC1B,GAAG,OAAO,CAAC,MAAM,CAAC;IA8Cb,6BAA6B,CAAC,KAAK,EAAE;QACzC,gBAAgB,EAAE,MAAM,CAAC;QACzB,YAAY,EAAE,MAAM,CAAC;QACrB,eAAe,EAAE,OAAO,CAAC;QACzB,KAAK,EAAE,0BAA0B,GAAG,IAAI,CAAC;QACzC,KAAK,EAAE,MAAM,CAAC;KACf,GAAG,OAAO,CAAC,6BAA6B,EAAE,CAAC;CAgD7C"}