@hasna/todos 0.15.35 → 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.
Files changed (92) hide show
  1. package/dist/cli/cloud-router.d.ts +52 -18
  2. package/dist/cli/cloud-router.d.ts.map +1 -1
  3. package/dist/cli/commands/agent-commands.d.ts.map +1 -1
  4. package/dist/cli/commands/plan-template-commands.d.ts.map +1 -1
  5. package/dist/cli/commands/project-commands.d.ts.map +1 -1
  6. package/dist/cli/commands/task-commands.d.ts.map +1 -1
  7. package/dist/cli/commands/task-subtree-transfer-commands.d.ts +3 -0
  8. package/dist/cli/commands/task-subtree-transfer-commands.d.ts.map +1 -0
  9. package/dist/cli/index.js +3430 -827
  10. package/dist/cli/stage-a.d.ts.map +1 -1
  11. package/dist/contracts.js +66 -58
  12. package/dist/db/plan-comments.d.ts +14 -0
  13. package/dist/db/plan-comments.d.ts.map +1 -0
  14. package/dist/db/schema.d.ts.map +1 -1
  15. package/dist/db/task-crud.d.ts.map +1 -1
  16. package/dist/db/task-graph.d.ts.map +1 -1
  17. package/dist/db/task-lifecycle.d.ts.map +1 -1
  18. package/dist/db/task-lists.d.ts.map +1 -1
  19. package/dist/db/task-relations.d.ts.map +1 -1
  20. package/dist/index.d.ts +3 -2
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +1897 -334
  23. package/dist/lib/comment-cursor.d.ts +19 -0
  24. package/dist/lib/comment-cursor.d.ts.map +1 -1
  25. package/dist/lib/import-export-bridge.d.ts.map +1 -1
  26. package/dist/lib/instant-compare.d.ts +35 -0
  27. package/dist/lib/instant-compare.d.ts.map +1 -0
  28. package/dist/mcp/index.js +2403 -501
  29. package/dist/mcp.js +8 -4
  30. package/dist/pr-groups/postgres.d.ts.map +1 -1
  31. package/dist/project-registration/postgres.d.ts.map +1 -1
  32. package/dist/project-registration.js +290 -81
  33. package/dist/registry.d.ts +1 -1
  34. package/dist/registry.d.ts.map +1 -1
  35. package/dist/registry.js +73 -58
  36. package/dist/release-provenance.json +5 -5
  37. package/dist/sdk/index.d.ts +1 -1
  38. package/dist/sdk/index.d.ts.map +1 -1
  39. package/dist/sdk/index.js +35 -0
  40. package/dist/sdk/v1.generated.d.ts +141 -0
  41. package/dist/sdk/v1.generated.d.ts.map +1 -1
  42. package/dist/server/cloud.d.ts +12 -2
  43. package/dist/server/cloud.d.ts.map +1 -1
  44. package/dist/server/index.js +5161 -8425
  45. package/dist/server/openapi.d.ts +2714 -1937
  46. package/dist/server/openapi.d.ts.map +1 -1
  47. package/dist/server/v1.d.ts +2 -1
  48. package/dist/server/v1.d.ts.map +1 -1
  49. package/dist/storage/config.d.ts +12 -30
  50. package/dist/storage/config.d.ts.map +1 -1
  51. package/dist/storage/index.d.ts +2 -2
  52. package/dist/storage/index.d.ts.map +1 -1
  53. package/dist/storage/interfaces.d.ts +13 -2
  54. package/dist/storage/interfaces.d.ts.map +1 -1
  55. package/dist/storage/local-sqlite.d.ts.map +1 -1
  56. package/dist/storage/postgres-adapter.d.ts.map +1 -1
  57. package/dist/storage/timestamp-backfill.d.ts +62 -0
  58. package/dist/storage/timestamp-backfill.d.ts.map +1 -0
  59. package/dist/storage.d.ts +2 -2
  60. package/dist/storage.d.ts.map +1 -1
  61. package/dist/storage.js +268 -74
  62. package/dist/task-manifest/postgres.d.ts.map +1 -1
  63. package/dist/task-manifest.js +15 -6
  64. package/dist/task-subtree-transfer/authority.d.ts +27 -0
  65. package/dist/task-subtree-transfer/authority.d.ts.map +1 -0
  66. package/dist/task-subtree-transfer/backend.d.ts +9 -0
  67. package/dist/task-subtree-transfer/backend.d.ts.map +1 -0
  68. package/dist/task-subtree-transfer/canonical.d.ts +14 -0
  69. package/dist/task-subtree-transfer/canonical.d.ts.map +1 -0
  70. package/dist/task-subtree-transfer/http.d.ts +16 -0
  71. package/dist/task-subtree-transfer/http.d.ts.map +1 -0
  72. package/dist/task-subtree-transfer/index.d.ts +6 -0
  73. package/dist/task-subtree-transfer/index.d.ts.map +1 -0
  74. package/dist/task-subtree-transfer/planner.d.ts +24 -0
  75. package/dist/task-subtree-transfer/planner.d.ts.map +1 -0
  76. package/dist/task-subtree-transfer/postgres.d.ts +19 -0
  77. package/dist/task-subtree-transfer/postgres.d.ts.map +1 -0
  78. package/dist/task-subtree-transfer/schema-sql.d.ts +5 -0
  79. package/dist/task-subtree-transfer/schema-sql.d.ts.map +1 -0
  80. package/dist/task-subtree-transfer/sqlite.d.ts +16 -0
  81. package/dist/task-subtree-transfer/sqlite.d.ts.map +1 -0
  82. package/dist/task-subtree-transfer/types.d.ts +148 -0
  83. package/dist/task-subtree-transfer/types.d.ts.map +1 -0
  84. package/dist/task-subtree-transfer.d.ts +2 -0
  85. package/dist/task-subtree-transfer.d.ts.map +1 -0
  86. package/dist/task-subtree-transfer.js +1878 -0
  87. package/dist/testing.d.ts +41 -7
  88. package/dist/testing.d.ts.map +1 -1
  89. package/dist/testing.js +44 -1
  90. package/dist/types/index.d.ts +24 -0
  91. package/dist/types/index.d.ts.map +1 -1
  92. package/package.json +8 -4
package/dist/index.js CHANGED
@@ -2676,6 +2676,20 @@ function ensureSchema(db) {
2676
2676
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_webhook_deliveries_event ON webhook_deliveries(event)");
2677
2677
  ensureColumn("task_comments", "type", "TEXT DEFAULT 'comment'");
2678
2678
  ensureColumn("task_comments", "progress_pct", "INTEGER");
2679
+ ensureTable("plan_comments", `
2680
+ CREATE TABLE plan_comments (
2681
+ id TEXT PRIMARY KEY,
2682
+ plan_id TEXT NOT NULL REFERENCES plans(id) ON DELETE CASCADE,
2683
+ agent_id TEXT,
2684
+ session_id TEXT,
2685
+ content TEXT NOT NULL,
2686
+ type TEXT DEFAULT 'comment' CHECK(type IN ('comment', 'progress', 'note')),
2687
+ progress_pct INTEGER CHECK(progress_pct IS NULL OR (progress_pct >= 0 AND progress_pct <= 100)),
2688
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
2689
+ machine_id TEXT,
2690
+ synced_at TEXT
2691
+ )`);
2692
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_plan_comments_plan ON plan_comments(plan_id)");
2679
2693
  ensureColumn("projects", "machine_id", "TEXT");
2680
2694
  ensureColumn("projects", "synced_at", "TEXT");
2681
2695
  ensureColumn("tasks", "machine_id", "TEXT");
@@ -3657,6 +3671,7 @@ var init_machines = __esm(() => {
3657
3671
  "task_lists",
3658
3672
  "plans",
3659
3673
  "task_comments",
3674
+ "plan_comments",
3660
3675
  "sessions",
3661
3676
  "task_history",
3662
3677
  "webhooks",
@@ -4160,20 +4175,16 @@ var init_sync_utils = __esm(() => {
4160
4175
  // src/storage/config.ts
4161
4176
  var exports_config = {};
4162
4177
  __export(exports_config, {
4163
- parseStorageMode: () => parseStorageMode,
4164
4178
  parseStorageBackend: () => parseStorageBackend,
4165
4179
  loadTodosStorageConfig: () => loadTodosStorageConfig,
4166
4180
  loadStorageConfig: () => loadStorageConfig,
4167
4181
  isTodosShadowEnabled: () => isTodosShadowEnabled,
4168
- isTodosRemoteStorageEnabled: () => isTodosRemoteStorageEnabled,
4169
4182
  isTodosPostgresBackend: () => isTodosPostgresBackend,
4170
4183
  getTodosStorageShadowEnvName: () => getTodosStorageShadowEnvName,
4171
- getTodosStorageMode: () => getTodosStorageMode,
4172
4184
  getTodosStorageEnvName: () => getTodosStorageEnvName,
4173
4185
  getTodosStorageDatabaseUrl: () => getTodosStorageDatabaseUrl,
4174
4186
  getTodosStorageDatabaseEnv: () => getTodosStorageDatabaseEnv,
4175
4187
  getTodosStorageBackend: () => getTodosStorageBackend,
4176
- getStorageMode: () => getStorageMode,
4177
4188
  getStorageDatabaseUrl: () => getStorageDatabaseUrl,
4178
4189
  getStorageDatabaseEnv: () => getStorageDatabaseEnv,
4179
4190
  getCanonicalTodosRdsConfig: () => getCanonicalTodosRdsConfig,
@@ -4183,7 +4194,6 @@ __export(exports_config, {
4183
4194
  TODOS_STORAGE_FALLBACK_ENV: () => TODOS_STORAGE_FALLBACK_ENV,
4184
4195
  TODOS_STORAGE_ENV: () => TODOS_STORAGE_ENV,
4185
4196
  STORAGE_TABLES: () => STORAGE_TABLES,
4186
- REMOVED_STORAGE_MODE_ENV_KEYS: () => REMOVED_STORAGE_MODE_ENV_KEYS,
4187
4197
  CANONICAL_TODOS_RDS_RUNTIME_PATH_ENV: () => CANONICAL_TODOS_RDS_RUNTIME_PATH_ENV,
4188
4198
  CANONICAL_TODOS_RDS_DATABASE: () => CANONICAL_TODOS_RDS_DATABASE,
4189
4199
  CANONICAL_TODOS_RDS_CLUSTER_ENV: () => CANONICAL_TODOS_RDS_CLUSTER_ENV
@@ -4238,9 +4248,6 @@ function loadStorageConfig(env = process.env) {
4238
4248
  function isTodosPostgresBackend(config) {
4239
4249
  return config.mode === "postgres";
4240
4250
  }
4241
- function isTodosRemoteStorageEnabled(config) {
4242
- return isTodosPostgresBackend(config);
4243
- }
4244
4251
  function assertTodosRemoteStorageConfig(config) {
4245
4252
  if (!isTodosPostgresBackend(config))
4246
4253
  return;
@@ -4256,32 +4263,15 @@ function parseStorageBackend(value) {
4256
4263
  return "sqlite";
4257
4264
  if (normalized === "postgres" || normalized === "postgresql")
4258
4265
  return "postgres";
4259
- if (["local", "remote", "cloud", "hybrid", "self_hosted"].includes(normalized)) {
4260
- throw new Error(`${TODOS_STORAGE_ENV.databaseUrl} selects the backend. Deployment modes no longer exist: ` + `delete the storage-mode variable and set ${TODOS_STORAGE_ENV.databaseUrl} to select the ` + `postgresql backend, or leave it unset for sqlite.`);
4261
- }
4262
4266
  throw new Error(`Storage backend must be sqlite or postgres`);
4263
4267
  }
4264
- function parseStorageMode(value) {
4265
- return parseStorageBackend(value);
4266
- }
4267
4268
  function getTodosStorageBackend(env = process.env) {
4268
- for (const key of REMOVED_STORAGE_MODE_ENV_KEYS) {
4269
- if (Object.hasOwn(env, key) && env[key] !== undefined) {
4270
- throw new Error(`${key} was removed. Deployment modes no longer exist: delete the storage-mode variable. ` + `Set ${TODOS_STORAGE_ENV.databaseUrl} to select the postgresql backend, ` + `or leave it unset for sqlite.`);
4271
- }
4272
- }
4273
4269
  if (!getTodosStorageDatabaseUrl(env))
4274
4270
  return "sqlite";
4275
4271
  if (isTodosShadowEnabled(env))
4276
4272
  return "sqlite";
4277
4273
  return "postgres";
4278
4274
  }
4279
- function getTodosStorageMode(env = process.env) {
4280
- return getTodosStorageBackend(env);
4281
- }
4282
- function getStorageMode(env = process.env) {
4283
- return getTodosStorageBackend(env);
4284
- }
4285
4275
  function isTodosShadowEnabled(env = process.env) {
4286
4276
  return parseBoolean(readStorageEnv(env, "shadow").value, false);
4287
4277
  }
@@ -4348,7 +4338,7 @@ function parsePositiveInteger(value, fallback) {
4348
4338
  }
4349
4339
  return parsed;
4350
4340
  }
4351
- var TODOS_STORAGE_TABLES, STORAGE_TABLES, TODOS_STORAGE_ENV, TODOS_STORAGE_FALLBACK_ENV, REMOVED_STORAGE_MODE_ENV_KEYS, CANONICAL_TODOS_RDS_CLUSTER_ENV = "HASNA_TODOS_RDS_CLUSTER", CANONICAL_TODOS_RDS_RUNTIME_PATH_ENV = "HASNA_TODOS_RDS_RUNTIME_PATH", CANONICAL_TODOS_RDS_DATABASE = "todos";
4341
+ var TODOS_STORAGE_TABLES, STORAGE_TABLES, TODOS_STORAGE_ENV, TODOS_STORAGE_FALLBACK_ENV, CANONICAL_TODOS_RDS_CLUSTER_ENV = "HASNA_TODOS_RDS_CLUSTER", CANONICAL_TODOS_RDS_RUNTIME_PATH_ENV = "HASNA_TODOS_RDS_RUNTIME_PATH", CANONICAL_TODOS_RDS_DATABASE = "todos";
4352
4342
  var init_config = __esm(() => {
4353
4343
  TODOS_STORAGE_TABLES = [
4354
4344
  "todos_sync_records",
@@ -4387,12 +4377,6 @@ var init_config = __esm(() => {
4387
4377
  syncBatchSize: "TODOS_SYNC_BATCH_SIZE",
4388
4378
  syncDryRun: "TODOS_SYNC_DRY_RUN"
4389
4379
  };
4390
- REMOVED_STORAGE_MODE_ENV_KEYS = [
4391
- "HASNA_TODOS_STORAGE_MODE",
4392
- "HASNA_TODOS_MODE",
4393
- "TODOS_STORAGE_MODE",
4394
- "TODOS_MODE"
4395
- ];
4396
4380
  });
4397
4381
 
4398
4382
  // src/storage/shadow-outbox-schema.ts
@@ -6857,22 +6841,31 @@ function updateTaskList(id, input, db) {
6857
6841
  throw new TaskListNotFoundError(id);
6858
6842
  const sets = ["updated_at = ?"];
6859
6843
  const params = [now()];
6860
- if (input.slug !== undefined) {
6861
- const slug = slugify(input.slug);
6862
- if (!slug)
6863
- throw new Error("Invalid task-list slug \u2014 must be non-empty kebab-case");
6864
- const duplicate = existing.project_id ? d.query("SELECT id FROM task_lists WHERE project_id = ? AND slug = ? AND id != ?").get(existing.project_id, slug, id) : d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ? AND id != ?").get(slug, id);
6844
+ const projectId = input.project_id === "" || input.project_id === null ? null : input.project_id === undefined ? existing.project_id : input.project_id;
6845
+ const scopeChanged = projectId !== existing.project_id;
6846
+ const effectiveSlug = input.slug !== undefined ? slugify(input.slug) : existing.slug;
6847
+ if (input.slug !== undefined && !effectiveSlug) {
6848
+ throw new Error("Invalid task-list slug \u2014 must be non-empty kebab-case");
6849
+ }
6850
+ if (scopeChanged || input.slug !== undefined && effectiveSlug !== existing.slug) {
6851
+ const duplicate = projectId ? d.query("SELECT id FROM task_lists WHERE project_id = ? AND slug = ? AND id != ?").get(projectId, effectiveSlug, id) : d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ? AND id != ?").get(effectiveSlug, id);
6865
6852
  if (duplicate) {
6866
- throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${slug}" already exists in this scope`);
6853
+ throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${effectiveSlug}" already exists in this scope`);
6867
6854
  }
6868
- if (slug !== existing.slug) {
6855
+ if (scopeChanged || effectiveSlug !== existing.slug) {
6869
6856
  releaseCanonicalSlugClaims("task_list", id, d);
6870
- if (!claimCanonicalSlug("task_list", taskListSlugScopeKey(existing.project_id), slug, id, d)) {
6871
- throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${slug}" already exists in this scope`);
6857
+ if (!claimCanonicalSlug("task_list", taskListSlugScopeKey(projectId), effectiveSlug, id, d)) {
6858
+ throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${effectiveSlug}" already exists in this scope`);
6872
6859
  }
6873
6860
  }
6861
+ }
6862
+ if (input.slug !== undefined) {
6874
6863
  sets.push("slug = ?");
6875
- params.push(slug);
6864
+ params.push(effectiveSlug);
6865
+ }
6866
+ if (projectId !== existing.project_id) {
6867
+ sets.push("project_id = ?");
6868
+ params.push(projectId);
6876
6869
  }
6877
6870
  if (input.name !== undefined) {
6878
6871
  sets.push("name = ?");
@@ -8658,7 +8651,7 @@ function getTaskGraph(taskId, direction = "both", db) {
8658
8651
  const deps = getTaskDependencies(t.id, d);
8659
8652
  const hasUnfinishedDeps = deps.some((dep) => {
8660
8653
  const depTask = getTask(dep.depends_on, d);
8661
- return depTask && depTask.status !== "completed";
8654
+ return depTask && isBlockingDependencyStatus(depTask.status);
8662
8655
  });
8663
8656
  return { id: t.id, short_id: t.short_id, title: t.title, status: t.status, priority: t.priority, is_blocked: hasUnfinishedDeps };
8664
8657
  }
@@ -8918,7 +8911,7 @@ function getBlockingDeps(id, db) {
8918
8911
  const blocking = [];
8919
8912
  for (const dep of deps) {
8920
8913
  const task = getTask(dep.depends_on, d);
8921
- if (task && task.status !== "completed")
8914
+ if (task && isBlockingDependencyStatus(task.status))
8922
8915
  blocking.push(task);
8923
8916
  }
8924
8917
  return blocking;
@@ -9242,7 +9235,7 @@ function getNextTask(agentId, filters, db) {
9242
9235
  conditions.push(`id IN (SELECT task_id FROM task_tags WHERE tag IN (${placeholders}))`);
9243
9236
  params.push(...filters.tags);
9244
9237
  }
9245
- 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'))");
9246
9239
  const where = conditions.join(" AND ");
9247
9240
  let recentProjectIds = [];
9248
9241
  const assignedAliasParams = [];
@@ -9286,7 +9279,7 @@ function getActiveWork(filters, db) {
9286
9279
  }
9287
9280
  function getTasksChangedSince(since, filters, db) {
9288
9281
  const d = db || getDatabase();
9289
- const conditions = ["updated_at > ?"];
9282
+ const conditions = ["(julianday(updated_at) IS NULL OR julianday(updated_at) > julianday(?))"];
9290
9283
  const params = [since];
9291
9284
  if (filters?.project_id) {
9292
9285
  conditions.push("project_id = ?");
@@ -9321,8 +9314,8 @@ function failTask(id, agentId, reason, options, db) {
9321
9314
  const safeMeta = sanitizePreWriteValue(meta, "task.failure.metadata");
9322
9315
  const timestamp2 = now();
9323
9316
  const failTx = d.transaction(() => {
9324
- const res = d.run(`UPDATE tasks SET status = 'failed', reason = ?, locked_by = NULL, locked_at = NULL, metadata = ?, version = version + 1, updated_at = ?
9325
- WHERE id = ? AND version = ?`, [safeReason, JSON.stringify(safeMeta), timestamp2, id, task.version]);
9317
+ const res = d.run(`UPDATE tasks SET status = 'failed', reason = ?, locked_by = NULL, locked_at = NULL, completed_at = COALESCE(completed_at, ?), metadata = ?, version = version + 1, updated_at = ?
9318
+ WHERE id = ? AND version = ?`, [safeReason, timestamp2, JSON.stringify(safeMeta), timestamp2, id, task.version]);
9326
9319
  if (res.changes === 0) {
9327
9320
  const current = getTask(id, d);
9328
9321
  throw new VersionConflictError(id, task.version, current?.version ?? -1);
@@ -9334,6 +9327,7 @@ function failTask(id, agentId, reason, options, db) {
9334
9327
  status: "failed",
9335
9328
  locked_by: null,
9336
9329
  locked_at: null,
9330
+ completed_at: task.completed_at ?? timestamp2,
9337
9331
  reason: safeReason,
9338
9332
  metadata: safeMeta,
9339
9333
  version: task.version + 1,
@@ -9982,13 +9976,20 @@ function updateTaskStored(id, input, db) {
9982
9976
  if (isTerminalStatus(input.status)) {
9983
9977
  sets.push("locked_by = NULL");
9984
9978
  sets.push("locked_at = NULL");
9985
- }
9986
- if (input.status === "completed") {
9987
- sets.push("completed_at = ?");
9988
- params.push(completionTimestamp);
9979
+ if (input.status === "completed") {
9980
+ sets.push("completed_at = ?");
9981
+ params.push(completionTimestamp);
9982
+ } else {
9983
+ sets.push("completed_at = COALESCE(completed_at, ?)");
9984
+ params.push(completionTimestamp);
9985
+ }
9989
9986
  } else if (task.status === "completed" && input.completed_at === undefined) {
9990
9987
  sets.push("completed_at = NULL");
9991
9988
  }
9989
+ if (input.status === "in_progress") {
9990
+ sets.push("started_at = COALESCE(started_at, ?)");
9991
+ params.push(timestamp2);
9992
+ }
9992
9993
  }
9993
9994
  if (input.priority !== undefined) {
9994
9995
  sets.push("priority = ?");
@@ -10126,7 +10127,6 @@ function updateTaskStored(id, input, db) {
10126
10127
  if (input.approved_by !== undefined)
10127
10128
  logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
10128
10129
  const reopened = input.status !== undefined && input.status !== "completed" && task.status === "completed" && input.completed_at === undefined;
10129
- const completedNow = input.status === "completed";
10130
10130
  const terminalNow = input.status !== undefined && isTerminalStatus(input.status);
10131
10131
  const updatedTask = {
10132
10132
  ...task,
@@ -10137,7 +10137,8 @@ function updateTaskStored(id, input, db) {
10137
10137
  updated_at: timestamp2,
10138
10138
  locked_by: terminalNow ? null : task.locked_by,
10139
10139
  locked_at: terminalNow ? null : task.locked_at,
10140
- completed_at: completedNow ? completionTimestamp : reopened ? null : input.completed_at !== undefined ? input.completed_at : task.completed_at,
10140
+ completed_at: input.status === "completed" ? completionTimestamp : input.completed_at !== undefined ? input.completed_at : terminalNow ? task.completed_at ?? completionTimestamp : reopened ? null : task.completed_at,
10141
+ started_at: input.status === "in_progress" ? task.started_at ?? timestamp2 : task.started_at,
10141
10142
  sla_minutes: input.sla_minutes !== undefined ? input.sla_minutes : task.sla_minutes,
10142
10143
  actual_minutes: input.actual_minutes ?? task.actual_minutes,
10143
10144
  confidence: input.confidence !== undefined ? input.confidence : task.confidence,
@@ -10198,6 +10199,9 @@ function deleteTask(id, db) {
10198
10199
  version: row.version
10199
10200
  }, d);
10200
10201
  const result = d.run("DELETE FROM tasks WHERE id = ?", [id]);
10202
+ if (result.changes > 0) {
10203
+ d.run("DELETE FROM task_dependencies WHERE task_id = ? OR depends_on = ?", [id, id]);
10204
+ }
10201
10205
  return result.changes > 0;
10202
10206
  }
10203
10207
  var init_task_crud = __esm(() => {
@@ -12882,10 +12886,10 @@ function cancelDispatch(id, db) {
12882
12886
  _db2.run("UPDATE dispatches SET status = 'cancelled' WHERE id = ?", [id]);
12883
12887
  return getDispatch(id, _db2);
12884
12888
  }
12885
- function updateDispatchStatus(id, status2, opts = {}, db) {
12889
+ function updateDispatchStatus(id, status3, opts = {}, db) {
12886
12890
  const _db2 = db ?? getDatabase();
12887
12891
  const error = opts.error ? sanitizePreWriteText(opts.error, "dispatch.error") : null;
12888
- _db2.run("UPDATE dispatches SET status = ?, error = ?, sent_at = ? WHERE id = ?", [status2, error, opts.sent_at ?? null, id]);
12892
+ _db2.run("UPDATE dispatches SET status = ?, error = ?, sent_at = ? WHERE id = ?", [status3, error, opts.sent_at ?? null, id]);
12889
12893
  }
12890
12894
  function createDispatchLog(log, db) {
12891
12895
  const _db2 = db ?? getDatabase();
@@ -12918,7 +12922,7 @@ var init_dispatches = __esm(() => {
12918
12922
  // package.json
12919
12923
  var package_default = {
12920
12924
  name: "@hasna/todos",
12921
- version: "0.15.35",
12925
+ version: "0.15.46",
12922
12926
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
12923
12927
  type: "module",
12924
12928
  main: "dist/index.js",
@@ -12964,6 +12968,10 @@ var package_default = {
12964
12968
  "./task-manifest": {
12965
12969
  types: "./dist/task-manifest.d.ts",
12966
12970
  import: "./dist/task-manifest.js"
12971
+ },
12972
+ "./task-subtree-transfer": {
12973
+ types: "./dist/task-subtree-transfer.d.ts",
12974
+ import: "./dist/task-subtree-transfer.js"
12967
12975
  }
12968
12976
  },
12969
12977
  workspaces: [
@@ -12977,8 +12985,8 @@ var package_default = {
12977
12985
  "README.md"
12978
12986
  ],
12979
12987
  scripts: {
12980
- build: "rm -rf dist dashboard/dist && cd dashboard && bun install --frozen-lockfile && bun run build && cd .. && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts src/project-registration.ts src/task-manifest.ts --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && tsc --emitDeclarationOnly --outDir dist",
12981
- "build:server": "rm -rf dist && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts src/project-registration.ts src/task-manifest.ts --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*'",
12988
+ build: "rm -rf dist dashboard/dist && cd dashboard && bun install --frozen-lockfile && bun run build && cd .. && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts src/project-registration.ts src/task-manifest.ts src/task-subtree-transfer.ts --outdir dist --root src --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && tsc --emitDeclarationOnly --outDir dist",
12989
+ "build:server": "rm -rf dist && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts src/project-registration.ts src/task-manifest.ts src/task-subtree-transfer.ts --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*'",
12982
12990
  migrate: "bun run src/server/index.ts migrate",
12983
12991
  "backfill:comment-redaction": "bun run src/server/index.ts redact-comments",
12984
12992
  "generate:sdk": "bun run scripts/generate-sdk.ts",
@@ -13032,7 +13040,7 @@ var package_default = {
13032
13040
  author: "Andrei Hasna <andrei@hasna.com>",
13033
13041
  license: "Apache-2.0",
13034
13042
  dependencies: {
13035
- "@hasna/contracts": "0.5.2",
13043
+ "@hasna/contracts": "0.13.4",
13036
13044
  "@hasna/events": "^0.1.11",
13037
13045
  "@modelcontextprotocol/sdk": "^1.12.1",
13038
13046
  chalk: "^5.4.1",
@@ -25218,6 +25226,13 @@ var TODOS_PACKAGE_EXPORTS = [
25218
25226
  types: "./dist/task-manifest.d.ts",
25219
25227
  description: "Transactional package-owned authority for deterministic plan and task graph manifests.",
25220
25228
  stability: "stable"
25229
+ },
25230
+ {
25231
+ subpath: "./task-subtree-transfer",
25232
+ import: "./dist/task-subtree-transfer.js",
25233
+ types: "./dist/task-subtree-transfer.d.ts",
25234
+ description: "Atomic package-owned authority for existing task-subtree project transfers and rollback.",
25235
+ stability: "stable"
25221
25236
  }
25222
25237
  ];
25223
25238
  function source8(version) {
@@ -25410,6 +25425,40 @@ init_tasks();
25410
25425
  init_projects();
25411
25426
  init_plans();
25412
25427
 
25428
+ // src/db/plan-comments.ts
25429
+ init_types();
25430
+ init_database();
25431
+ init_plans();
25432
+ init_prewrite_secrets();
25433
+ function addPlanComment(input, db) {
25434
+ const d = db || getDatabase();
25435
+ if (!getPlan(input.plan_id, d)) {
25436
+ throw new PlanNotFoundError(input.plan_id);
25437
+ }
25438
+ const id = uuid();
25439
+ const timestamp3 = now();
25440
+ d.run(`INSERT INTO plan_comments (id, plan_id, agent_id, session_id, content, type, progress_pct, created_at)
25441
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
25442
+ id,
25443
+ input.plan_id,
25444
+ input.agent_id || null,
25445
+ input.session_id || null,
25446
+ sanitizePreWriteText(input.content, "comment.content"),
25447
+ input.type || "comment",
25448
+ input.progress_pct ?? null,
25449
+ timestamp3
25450
+ ]);
25451
+ return getPlanComment(id, d);
25452
+ }
25453
+ function getPlanComment(id, db) {
25454
+ const d = db || getDatabase();
25455
+ return d.query("SELECT * FROM plan_comments WHERE id = ?").get(id);
25456
+ }
25457
+ function listPlanComments(planId, db) {
25458
+ const d = db || getDatabase();
25459
+ return d.query("SELECT * FROM plan_comments WHERE plan_id = ? ORDER BY created_at, rowid").all(planId);
25460
+ }
25461
+
25413
25462
  // src/db/agents.ts
25414
25463
  init_types();
25415
25464
  init_database();
@@ -26067,6 +26116,20 @@ init_task_lists();
26067
26116
  init_templates();
26068
26117
  init_audit();
26069
26118
  init_comments();
26119
+
26120
+ // src/lib/comment-cursor.ts
26121
+ function isStrictlyOlder(comment, before) {
26122
+ return comment.created_at < before.created_at || comment.created_at === before.created_at && comment.id < before.id;
26123
+ }
26124
+ function compareCommentKeyset(left, right) {
26125
+ if (isStrictlyOlder(left, right))
26126
+ return -1;
26127
+ if (isStrictlyOlder(right, left))
26128
+ return 1;
26129
+ return 0;
26130
+ }
26131
+
26132
+ // src/storage/local-sqlite.ts
26070
26133
  init_database();
26071
26134
 
26072
26135
  // src/db/integrity.ts
@@ -27087,7 +27150,7 @@ function matchesExtraFilters(task2, filter) {
27087
27150
  }
27088
27151
  if (filter.tags?.length) {
27089
27152
  const taskTags2 = new Set(task2.tags ?? []);
27090
- if (!filter.tags.every((tag) => taskTags2.has(tag)))
27153
+ if (!filter.tags.some((tag) => taskTags2.has(tag)))
27091
27154
  return false;
27092
27155
  }
27093
27156
  if (filter.include_subtasks !== true && filter.parent_id === undefined && task2.parent_id)
@@ -27163,7 +27226,22 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
27163
27226
  list: (projectId) => listPlans(projectId, database()),
27164
27227
  update: (id, input) => updatePlan(id, input, database()),
27165
27228
  completeAtRevision: (id, expectedUpdatedAt) => completePlanAtRevision(id, expectedUpdatedAt, database()),
27166
- delete: (id) => deletePlan(id, database())
27229
+ delete: (id) => deletePlan(id, database()),
27230
+ addComment: (input) => addPlanComment(input, database()),
27231
+ getComments: (planId) => listPlanComments(planId, database()),
27232
+ getCommentsPage: (planId, options2) => {
27233
+ if (options2?.limit !== undefined && (!Number.isSafeInteger(options2.limit) || options2.limit < 1 || options2.limit > 1001)) {
27234
+ throw new Error("Plan comment limit must be an integer between 1 and 1001");
27235
+ }
27236
+ let comments = listPlanComments(planId, database());
27237
+ comments = comments.sort(compareCommentKeyset);
27238
+ if (options2?.before) {
27239
+ const before = options2.before;
27240
+ comments = comments.filter((comment) => isStrictlyOlder(comment, before));
27241
+ }
27242
+ const limit = options2?.limit ?? 100;
27243
+ return comments.slice(-limit);
27244
+ }
27167
27245
  },
27168
27246
  planProjectLinks: {
27169
27247
  apply: (input) => applyPlanProjectLinkSqlite(input, database()),
@@ -27204,10 +27282,10 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
27204
27282
  throw new Error("Comment limit must be an integer between 1 and 1001");
27205
27283
  }
27206
27284
  let comments = listComments(taskId, database());
27207
- comments = comments.sort((left, right) => left.created_at.localeCompare(right.created_at) || left.id.localeCompare(right.id));
27285
+ comments = comments.sort(compareCommentKeyset);
27208
27286
  if (options2?.before) {
27209
27287
  const before = options2.before;
27210
- comments = comments.filter((comment) => comment.created_at < before.created_at || comment.created_at === before.created_at && comment.id < before.id);
27288
+ comments = comments.filter((comment) => isStrictlyOlder(comment, before));
27211
27289
  }
27212
27290
  if (options2?.limit !== undefined)
27213
27291
  comments = comments.slice(-options2.limit);
@@ -27236,6 +27314,59 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
27236
27314
  init_types();
27237
27315
  import { randomUUID as randomUUID3 } from "crypto";
27238
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
27239
27370
  init_stale_lock_handoff();
27240
27371
 
27241
27372
  // src/storage/postgres-sync.ts
@@ -27737,7 +27868,26 @@ function createPostgresTodosStorageAdapter(options) {
27737
27868
  list: async (projectId) => (await store.list("plans")).filter((plan) => projectId === undefined || plan.project_id === projectId).sort((a, b) => a.name.localeCompare(b.name)),
27738
27869
  update: (id, input) => updatePlan2(id, input, store),
27739
27870
  completeAtRevision: (id, expectedUpdatedAt, context) => store.completePlanAtRevision(id, expectedUpdatedAt, context),
27740
- delete: (id, context) => store.deletePlan(id, context)
27871
+ delete: (id, context) => store.deletePlan(id, context),
27872
+ addComment: (input, context) => addPlanComment2(input, store, context),
27873
+ getComments: async (planId) => {
27874
+ const pages = [];
27875
+ let before;
27876
+ while (true) {
27877
+ const page = await store.listPlanComments(planId, { limit: 1000, ...before ? { before } : {} });
27878
+ if (page.length === 0)
27879
+ break;
27880
+ pages.unshift(page);
27881
+ if (page.length < 1000)
27882
+ break;
27883
+ const oldest = page[0];
27884
+ before = { created_at: oldest.created_at, id: oldest.id };
27885
+ }
27886
+ return pages.flat().map(redactPlanComment).sort((a, b) => a.created_at.localeCompare(b.created_at) || a.id.localeCompare(b.id));
27887
+ },
27888
+ getCommentsPage: async (planId, options2) => {
27889
+ return (await store.listPlanComments(planId, options2)).map(redactPlanComment).sort((a, b) => a.created_at.localeCompare(b.created_at) || a.id.localeCompare(b.id));
27890
+ }
27741
27891
  },
27742
27892
  planProjectLinks: {
27743
27893
  apply: (input, context) => store.applyPlanProjectLink(input, context),
@@ -27831,11 +27981,16 @@ class PostgresJsonRecordStore {
27831
27981
  return context?.requestId ?? this.sourceMachineId ?? null;
27832
27982
  }
27833
27983
  async ensureSchema() {
27834
- this.schemaReady ??= (async () => {
27835
- for (const sql of postgresTodosSyncSchemaSql(this.tableName, this.cursorTableName)) {
27836
- await this.options.client.query(sql);
27837
- }
27838
- })();
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
+ }
27839
27994
  await this.schemaReady;
27840
27995
  }
27841
27996
  async get(type, id) {
@@ -27955,6 +28110,27 @@ class PostgresJsonRecordStore {
27955
28110
  LIMIT $${params.length}`, params);
27956
28111
  return result.rows.map((row) => payloadRecord2(row.payload)).reverse();
27957
28112
  }
28113
+ async listPlanComments(planId, options = {}) {
28114
+ await this.ensureSchema();
28115
+ const limit = options.limit ?? 100;
28116
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1001) {
28117
+ throw new Error("Postgres plan comment limit must be an integer between 1 and 1001");
28118
+ }
28119
+ const params = [this.service, planId];
28120
+ let cursorPredicate = "";
28121
+ if (options.before) {
28122
+ params.push(options.before.created_at, options.before.id);
28123
+ cursorPredicate = `AND (payload->>'created_at', object_id) < ($3, $4)`;
28124
+ }
28125
+ params.push(limit);
28126
+ const result = await this.options.client.query(`/* todos:list-plan-comments */ SELECT payload FROM ${this.tableName}
28127
+ WHERE service = $1 AND object_type = 'plan_comments' AND deleted_at IS NULL
28128
+ AND payload->>'plan_id' = $2
28129
+ ${cursorPredicate}
28130
+ ORDER BY payload->>'created_at' DESC, object_id DESC
28131
+ LIMIT $${params.length}`, params);
28132
+ return result.rows.map((row) => payloadRecord2(row.payload)).reverse();
28133
+ }
27958
28134
  async listRecords(type) {
27959
28135
  await this.ensureSchema();
27960
28136
  const result = await this.options.client.query(`SELECT object_type, object_id, payload, updated_at
@@ -29116,6 +29292,15 @@ class PostgresJsonRecordStore {
29116
29292
  WHERE r.service = $1 AND r.object_type = 'tasks' AND r.deleted_at IS NULL
29117
29293
  AND r.payload->>'plan_id' = $2 AND EXISTS (SELECT 1 FROM target)
29118
29294
  RETURNING 1
29295
+ ), deleted_plan_comments AS (
29296
+ UPDATE ${this.tableName} r SET
29297
+ deleted_at = $3::timestamptz,
29298
+ updated_at = $3::timestamptz,
29299
+ source_machine_id = COALESCE($4, r.source_machine_id),
29300
+ version = COALESCE(r.version, 0) + 1
29301
+ WHERE r.service = $1 AND r.object_type = 'plan_comments' AND r.deleted_at IS NULL
29302
+ AND r.payload->>'plan_id' = $2 AND EXISTS (SELECT 1 FROM target)
29303
+ RETURNING 1
29119
29304
  ), deleted_plan AS (
29120
29305
  UPDATE ${this.tableName} r SET
29121
29306
  deleted_at = $3::timestamptz,
@@ -29291,7 +29476,8 @@ async function updateTask2(id, input, store, context) {
29291
29476
  task_list_id: input.task_list_id !== undefined ? input.task_list_id : existing.task_list_id,
29292
29477
  parent_id: input.parent_id !== undefined ? input.parent_id : existing.parent_id,
29293
29478
  created_by: existing.created_by,
29294
- completed_at: reopened ? null : input.completed_at !== undefined ? input.completed_at : existing.completed_at
29479
+ completed_at: input.status === "completed" ? input.completed_at ?? new Date().toISOString() : input.completed_at !== undefined ? input.completed_at : input.status !== undefined && isTerminalStatus(input.status) ? existing.completed_at ?? new Date().toISOString() : reopened ? null : existing.completed_at,
29480
+ started_at: input.status === "in_progress" ? existing.started_at ?? new Date().toISOString() : existing.started_at
29295
29481
  };
29296
29482
  const storedTask = await store.upsertTaskWithPlanMembershipGuard(task2, [existing.plan_id, effectivePlanId].filter((planId) => Boolean(planId)), input.project_id !== undefined, context, {
29297
29483
  operation: "update",
@@ -29336,7 +29522,8 @@ async function failTask2(id, agentId, reason, options, store) {
29336
29522
  status: "failed",
29337
29523
  assigned_to: task2.assigned_to ?? agentId ?? null,
29338
29524
  reason: reason ?? task2.reason,
29339
- retry_after: options?.retry_after ?? task2.retry_after
29525
+ retry_after: options?.retry_after ?? task2.retry_after,
29526
+ completed_at: task2.completed_at ?? new Date().toISOString()
29340
29527
  }, store);
29341
29528
  if (!options?.retry)
29342
29529
  return { task: failed };
@@ -29568,7 +29755,7 @@ async function getActiveWork2(filters, store) {
29568
29755
  }));
29569
29756
  }
29570
29757
  async function getChangedSince(since, filters, store) {
29571
- return (await listTasks2(filters ?? {}, store)).filter((task2) => task2.updated_at > since);
29758
+ return (await listTasks2(filters ?? {}, store)).filter((task2) => changedSinceStampNewer(task2.updated_at ?? "", since));
29572
29759
  }
29573
29760
  async function createProject2(input, store, context) {
29574
29761
  const timestamp3 = new Date().toISOString();
@@ -29784,16 +29971,22 @@ async function createTaskList2(input, store, context) {
29784
29971
  async function updateTaskList2(id, input, store) {
29785
29972
  const list = await requireRecord("task_lists", id, store);
29786
29973
  const patch = definedPatch(input);
29974
+ const projectId = input.project_id === "" || input.project_id === null ? null : input.project_id === undefined ? list.project_id : input.project_id;
29787
29975
  if (input.slug !== undefined) {
29788
29976
  const slug = slugifyRaw(input.slug);
29789
29977
  if (!slug)
29790
29978
  throw new Error("Invalid task-list slug \u2014 must be non-empty kebab-case");
29791
- const duplicate = (await store.list("task_lists")).find((candidate) => candidate.id !== id && candidate.project_id === list.project_id && candidate.slug === slug);
29979
+ patch.slug = slug;
29980
+ }
29981
+ const effectiveSlug = patch.slug ?? list.slug;
29982
+ if (projectId !== list.project_id || effectiveSlug !== list.slug) {
29983
+ const duplicate = (await store.list("task_lists")).find((candidate) => candidate.id !== id && candidate.project_id === projectId && candidate.slug === effectiveSlug);
29792
29984
  if (duplicate) {
29793
- throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${slug}" already exists in this scope`);
29985
+ throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${effectiveSlug}" already exists in this scope`);
29794
29986
  }
29795
- patch.slug = slug;
29796
29987
  }
29988
+ if (projectId !== list.project_id)
29989
+ patch.project_id = projectId;
29797
29990
  return store.upsert("task_lists", {
29798
29991
  ...list,
29799
29992
  ...patch,
@@ -29886,6 +30079,22 @@ async function addComment2(input, store, context) {
29886
30079
  function redactComment(comment) {
29887
30080
  return { ...comment, content: redactEvidenceText(comment.content) };
29888
30081
  }
30082
+ async function addPlanComment2(input, store, context) {
30083
+ const comment = {
30084
+ id: randomUUID3(),
30085
+ plan_id: input.plan_id,
30086
+ agent_id: input.agent_id ?? context?.agentId ?? null,
30087
+ session_id: input.session_id ?? context?.sessionId ?? null,
30088
+ content: redactEvidenceText(input.content),
30089
+ type: input.type ?? "comment",
30090
+ progress_pct: input.progress_pct ?? null,
30091
+ created_at: new Date().toISOString()
30092
+ };
30093
+ return store.upsert("plan_comments", comment, context);
30094
+ }
30095
+ function redactPlanComment(comment) {
30096
+ return { ...comment, content: redactEvidenceText(comment.content) };
30097
+ }
29889
30098
  async function exportSnapshot(store) {
29890
30099
  return {
29891
30100
  exportedAt: new Date().toISOString(),
@@ -34901,14 +35110,23 @@ class PostgresTodosProjectRegistrationBackend {
34901
35110
  this.cursorTableName = safeIdentifier(options.cursorTableName ?? "todos_sync_cursors", "cursorTableName");
34902
35111
  }
34903
35112
  async ensureSchema() {
34904
- this.schemaReady ??= (async () => {
34905
- for (const statement of postgresTodosSyncSchemaSql(this.tableName, this.cursorTableName)) {
34906
- await this.client.query(statement);
34907
- }
34908
- for (const statement of postgresTodosProjectRegistrationSchemaSql()) {
34909
- 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;
34910
35128
  }
34911
- })();
35129
+ }
34912
35130
  await this.schemaReady;
34913
35131
  }
34914
35132
  async transaction(fn) {
@@ -42566,12 +42784,21 @@ class PostgresTodosTaskManifestBackend {
42566
42784
  this.tenantId = options.tenantId ?? "default";
42567
42785
  }
42568
42786
  async ensureSchema() {
42569
- this.schemaReady ??= (async () => {
42570
- for (const sql of postgresTodosSyncSchemaSql(this.tableName))
42571
- await this.client.query(sql);
42572
- for (const sql of postgresTodosTaskManifestSchemaSql(this.tenantId))
42573
- await this.client.query(sql);
42574
- })();
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
+ }
42575
42802
  await this.schemaReady;
42576
42803
  }
42577
42804
  async insertSync(tx, objectType2, objectId, payload, now4) {
@@ -43624,6 +43851,1323 @@ class TodosTaskManifestHttpClient {
43624
43851
  function createTodosTaskManifestHttpClient(options) {
43625
43852
  return new TodosTaskManifestHttpClient(options);
43626
43853
  }
43854
+ // src/task-subtree-transfer/types.ts
43855
+ var TODOS_TASK_SUBTREE_TRANSFER_ROUTE = "todos.task-subtree-transfer.v1";
43856
+ var TODOS_TASK_SUBTREE_TRANSFER_SCHEMA_VERSION = 1;
43857
+
43858
+ class TodosTaskSubtreeTransferError extends Error {
43859
+ code;
43860
+ details;
43861
+ constructor(code, message, details = {}) {
43862
+ super(message);
43863
+ this.code = code;
43864
+ this.details = details;
43865
+ this.name = "TodosTaskSubtreeTransferError";
43866
+ }
43867
+ }
43868
+
43869
+ // src/task-subtree-transfer/canonical.ts
43870
+ function taskSubtreeTransferRequestDigest(input) {
43871
+ const { idempotency_key: _idempotencyKey, ...request } = input;
43872
+ return canonicalDigest(request);
43873
+ }
43874
+ function taskSubtreeTransferRollbackRequestDigest(input) {
43875
+ const { idempotency_key: _idempotencyKey, ...request } = input;
43876
+ return canonicalDigest(request);
43877
+ }
43878
+ function deriveTodosTaskSubtreeTransferIdempotencyKey(input) {
43879
+ return `tstk_${canonicalDigest({
43880
+ route: TODOS_TASK_SUBTREE_TRANSFER_ROUTE,
43881
+ ...input
43882
+ }).slice(0, 48)}`;
43883
+ }
43884
+
43885
+ // src/task-subtree-transfer/planner.ts
43886
+ function sortedTaskImage(task3) {
43887
+ return {
43888
+ task_id: task3.task_id,
43889
+ project_id: task3.project_id,
43890
+ parent_id: task3.parent_id,
43891
+ plan_id: task3.plan_id,
43892
+ task_list_id: task3.task_list_id,
43893
+ version: task3.version,
43894
+ updated_at: task3.updated_at
43895
+ };
43896
+ }
43897
+ function sourcePopulationDigest(tasks) {
43898
+ return canonicalDigest(tasks.map((task3) => ({
43899
+ ...sortedTaskImage(task3),
43900
+ archived_at: task3.archived_at ?? null
43901
+ })).sort((left, right) => left.task_id.localeCompare(right.task_id)));
43902
+ }
43903
+ function closure(tasks, rootTaskId) {
43904
+ const byId = new Map(tasks.map((task3) => [task3.task_id, task3]));
43905
+ if (!byId.has(rootTaskId)) {
43906
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_NOT_FOUND", `Root task not found in the exact source project: ${rootTaskId}`, { root_task_id: rootTaskId });
43907
+ }
43908
+ const children = new Map;
43909
+ for (const task3 of tasks) {
43910
+ if (!task3.parent_id)
43911
+ continue;
43912
+ const siblings = children.get(task3.parent_id) ?? [];
43913
+ siblings.push(task3.task_id);
43914
+ children.set(task3.parent_id, siblings);
43915
+ }
43916
+ for (const ids2 of children.values())
43917
+ ids2.sort();
43918
+ const visiting = new Set;
43919
+ const visited = new Set;
43920
+ const result = [];
43921
+ const visit = (id) => {
43922
+ if (visiting.has(id)) {
43923
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_HIERARCHY_CYCLE", "Task hierarchy contains a cycle in the requested descendant closure", { task_id: id });
43924
+ }
43925
+ if (visited.has(id))
43926
+ return;
43927
+ const task3 = byId.get(id);
43928
+ if (!task3) {
43929
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_FOREIGN_REFERENCE", "Task hierarchy references a parent outside the exact source population", { task_id: id });
43930
+ }
43931
+ visiting.add(id);
43932
+ result.push(task3);
43933
+ for (const child of children.get(id) ?? [])
43934
+ visit(child);
43935
+ visiting.delete(id);
43936
+ visited.add(id);
43937
+ };
43938
+ visit(rootTaskId);
43939
+ return result.sort((left, right) => left.task_id.localeCompare(right.task_id));
43940
+ }
43941
+ function prepareTransfer(snapshot, input) {
43942
+ if (!snapshot.destination_project_found || !snapshot.destination_task_list_found || !snapshot.destination_parent_found) {
43943
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_FOREIGN_REFERENCE", "Destination project, task list, or parent does not match the exact requested target");
43944
+ }
43945
+ const moved = closure(snapshot.source_tasks, input.root_task_id);
43946
+ const movedIds = new Set(moved.map((task3) => task3.task_id));
43947
+ if (input.destination_parent_id && movedIds.has(input.destination_parent_id)) {
43948
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_HIERARCHY_CYCLE", "Destination parent cannot be inside the transferred subtree", { destination_parent_id: input.destination_parent_id });
43949
+ }
43950
+ const planIds = [...new Set(moved.map((task3) => task3.plan_id).filter((id) => Boolean(id)))].sort();
43951
+ const allPlanTasks = new Map;
43952
+ for (const task3 of snapshot.plan_tasks) {
43953
+ if (!task3.plan_id)
43954
+ continue;
43955
+ const rows = allPlanTasks.get(task3.plan_id) ?? [];
43956
+ rows.push(task3);
43957
+ allPlanTasks.set(task3.plan_id, rows);
43958
+ }
43959
+ const contained = [];
43960
+ const shared = [];
43961
+ const planById = new Map(snapshot.plans.map((plan) => [plan.plan_id, plan]));
43962
+ for (const planId of planIds) {
43963
+ const plan = planById.get(planId);
43964
+ if (!plan || plan.project_id !== input.source_project_id) {
43965
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_PLAN_CONFLICT", "A moved task references a plan absent from the exact source project", { plan_id: planId });
43966
+ }
43967
+ const members = allPlanTasks.get(planId) ?? [];
43968
+ if (members.length === 0) {
43969
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_PLAN_CONFLICT", "A moved task references a plan with no authoritative task membership", { plan_id: planId });
43970
+ }
43971
+ if (members.every((task3) => movedIds.has(task3.task_id)))
43972
+ contained.push(planId);
43973
+ else
43974
+ shared.push(planId);
43975
+ }
43976
+ const inspection = {
43977
+ source_project_id: input.source_project_id,
43978
+ destination_project_id: input.destination_project_id,
43979
+ destination_task_list_id: input.destination_task_list_id,
43980
+ root_task_id: input.root_task_id,
43981
+ destination_parent_id: input.destination_parent_id,
43982
+ expected_root_parent_id: moved.find((task3) => task3.task_id === input.root_task_id).parent_id,
43983
+ source_population_digest: sourcePopulationDigest(snapshot.source_tasks),
43984
+ expected_tasks: moved.map((task3) => ({ task_id: task3.task_id, version: task3.version })),
43985
+ contained_plan_ids: contained,
43986
+ shared_plan_ids: shared,
43987
+ complete: true
43988
+ };
43989
+ return {
43990
+ inspection,
43991
+ prior_tasks: moved.map(sortedTaskImage),
43992
+ prior_plans: contained.map((id) => planById.get(id)),
43993
+ task_plan_targets: new Map(moved.map((task3) => [task3.task_id, task3.plan_id]))
43994
+ };
43995
+ }
43996
+ function validateApplySnapshot(prepared, request, plans) {
43997
+ if (request.expected_root_parent_id !== prepared.inspection.expected_root_parent_id) {
43998
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_CAS_CONFLICT", "Root parent changed since inspection");
43999
+ }
44000
+ if (request.source_population_digest !== prepared.inspection.source_population_digest) {
44001
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_POPULATION_DRIFT", "Complete source-project task population changed since inspection");
44002
+ }
44003
+ const expected = request.expected_tasks.map((task3) => ({ task_id: task3.task_id, version: task3.version })).sort((left, right) => left.task_id.localeCompare(right.task_id));
44004
+ if (JSON.stringify(expected) !== JSON.stringify(prepared.inspection.expected_tasks)) {
44005
+ const actualById = new Map(prepared.inspection.expected_tasks.map((task3) => [task3.task_id, task3.version]));
44006
+ const sameIds = expected.length === prepared.inspection.expected_tasks.length && expected.every((task3) => actualById.has(task3.task_id));
44007
+ throw new TodosTaskSubtreeTransferError(sameIds ? "TODOS_TASK_SUBTREE_TRANSFER_CAS_CONFLICT" : "TODOS_TASK_SUBTREE_TRANSFER_CLOSURE_DRIFT", sameIds ? "At least one exact task version changed since inspection" : "Exact descendant closure changed since inspection");
44008
+ }
44009
+ const requestedSources = request.shared_plan_splits.map((mapping) => mapping.source_plan_id).sort();
44010
+ if (JSON.stringify(requestedSources) !== JSON.stringify(prepared.inspection.shared_plan_ids)) {
44011
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_PARTIAL_PLAN", "Shared plans require one exact explicit destination-plan mapping each", {
44012
+ expected_shared_plan_ids: prepared.inspection.shared_plan_ids,
44013
+ received_shared_plan_ids: requestedSources
44014
+ });
44015
+ }
44016
+ const planById = new Map(plans.map((plan) => [plan.plan_id, plan]));
44017
+ const destinationIds = new Set;
44018
+ for (const mapping of request.shared_plan_splits) {
44019
+ if (destinationIds.has(mapping.destination_plan_id)) {
44020
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_PLAN_CONFLICT", "Two shared source plans cannot collapse into one destination plan");
44021
+ }
44022
+ destinationIds.add(mapping.destination_plan_id);
44023
+ const destination = planById.get(mapping.destination_plan_id);
44024
+ if (!destination || destination.project_id !== request.destination_project_id || destination.task_list_id !== request.destination_task_list_id) {
44025
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_PLAN_CONFLICT", "Shared-plan destination does not match the exact destination project and task list", { destination_plan_id: mapping.destination_plan_id });
44026
+ }
44027
+ for (const [taskId, planId] of prepared.task_plan_targets) {
44028
+ if (planId === mapping.source_plan_id) {
44029
+ prepared.task_plan_targets.set(taskId, mapping.destination_plan_id);
44030
+ }
44031
+ }
44032
+ }
44033
+ }
44034
+
44035
+ // src/task-subtree-transfer/schema-sql.ts
44036
+ function sqliteTodosTaskSubtreeTransferSchemaSql() {
44037
+ return `
44038
+ CREATE TABLE IF NOT EXISTS todos_task_subtree_transfer_receipts (
44039
+ receipt_id TEXT PRIMARY KEY,
44040
+ tenant_id TEXT NOT NULL,
44041
+ kind TEXT NOT NULL CHECK(kind IN ('apply', 'rollback')),
44042
+ operation_id TEXT NOT NULL,
44043
+ step_id TEXT NOT NULL,
44044
+ idempotency_key TEXT NOT NULL,
44045
+ request_digest TEXT NOT NULL,
44046
+ precondition_digest TEXT NOT NULL,
44047
+ result_digest TEXT NOT NULL,
44048
+ apply_receipt_id TEXT,
44049
+ request_json TEXT NOT NULL,
44050
+ result_json TEXT NOT NULL,
44051
+ created_at TEXT NOT NULL,
44052
+ UNIQUE(tenant_id, kind, operation_id, step_id),
44053
+ UNIQUE(tenant_id, kind, idempotency_key)
44054
+ );
44055
+ CREATE TRIGGER IF NOT EXISTS todos_task_subtree_transfer_receipts_immutable_update
44056
+ BEFORE UPDATE ON todos_task_subtree_transfer_receipts BEGIN
44057
+ SELECT RAISE(ABORT, 'todos task subtree transfer receipts are immutable');
44058
+ END;
44059
+ CREATE TRIGGER IF NOT EXISTS todos_task_subtree_transfer_receipts_immutable_delete
44060
+ BEFORE DELETE ON todos_task_subtree_transfer_receipts BEGIN
44061
+ SELECT RAISE(ABORT, 'todos task subtree transfer receipts are immutable');
44062
+ END;
44063
+ `;
44064
+ }
44065
+ function ensureSqliteTodosTaskSubtreeTransferSchema(db) {
44066
+ db.exec(sqliteTodosTaskSubtreeTransferSchemaSql());
44067
+ }
44068
+ function postgresTodosTaskSubtreeTransferSchemaSql() {
44069
+ return [
44070
+ `CREATE TABLE IF NOT EXISTS todos_task_subtree_transfer_receipts (
44071
+ receipt_id text PRIMARY KEY,
44072
+ tenant_id text NOT NULL,
44073
+ kind text NOT NULL CHECK(kind IN ('apply', 'rollback')),
44074
+ operation_id text NOT NULL,
44075
+ step_id text NOT NULL,
44076
+ idempotency_key text NOT NULL,
44077
+ request_digest text NOT NULL,
44078
+ precondition_digest text NOT NULL,
44079
+ result_digest text NOT NULL,
44080
+ apply_receipt_id text,
44081
+ request_json jsonb NOT NULL,
44082
+ result_json jsonb NOT NULL,
44083
+ created_at timestamptz NOT NULL,
44084
+ UNIQUE(tenant_id, kind, operation_id, step_id),
44085
+ UNIQUE(tenant_id, kind, idempotency_key)
44086
+ )`,
44087
+ `CREATE OR REPLACE FUNCTION reject_todos_task_subtree_transfer_receipt_mutation()
44088
+ RETURNS trigger LANGUAGE plpgsql AS $$
44089
+ BEGIN
44090
+ RAISE EXCEPTION 'todos task subtree transfer receipts are immutable';
44091
+ END $$`,
44092
+ `DROP TRIGGER IF EXISTS todos_task_subtree_transfer_receipts_immutable_update
44093
+ ON todos_task_subtree_transfer_receipts`,
44094
+ `CREATE TRIGGER todos_task_subtree_transfer_receipts_immutable_update
44095
+ BEFORE UPDATE OR DELETE ON todos_task_subtree_transfer_receipts
44096
+ FOR EACH ROW EXECUTE FUNCTION reject_todos_task_subtree_transfer_receipt_mutation()`
44097
+ ];
44098
+ }
44099
+
44100
+ // src/task-subtree-transfer/postgres.ts
44101
+ function safeIdentifier3(value, fallback) {
44102
+ const candidate = value ?? fallback;
44103
+ if (!/^[a-z_][a-z0-9_]*$/.test(candidate)) {
44104
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "PostgreSQL table name must be a safe identifier");
44105
+ }
44106
+ return candidate;
44107
+ }
44108
+ function timestamp4(value) {
44109
+ return value instanceof Date ? value.toISOString() : new Date(String(value)).toISOString();
44110
+ }
44111
+ function payloadOf(row) {
44112
+ return typeof row.payload === "string" ? JSON.parse(row.payload) : row.payload;
44113
+ }
44114
+ function taskRecord(row) {
44115
+ const payload = payloadOf(row);
44116
+ return {
44117
+ task_id: String(payload.id ?? row.object_id),
44118
+ project_id: payload.project_id == null ? null : String(payload.project_id),
44119
+ parent_id: payload.parent_id == null ? null : String(payload.parent_id),
44120
+ plan_id: payload.plan_id == null ? null : String(payload.plan_id),
44121
+ task_list_id: payload.task_list_id == null ? null : String(payload.task_list_id),
44122
+ version: Number(payload.version ?? row.version ?? 1),
44123
+ updated_at: timestamp4(payload.updated_at ?? row.updated_at),
44124
+ archived_at: payload.archived_at == null ? null : String(payload.archived_at)
44125
+ };
44126
+ }
44127
+ function planRecord(row) {
44128
+ const payload = payloadOf(row);
44129
+ return {
44130
+ plan_id: String(payload.id ?? row.object_id),
44131
+ project_id: payload.project_id == null ? null : String(payload.project_id),
44132
+ task_list_id: payload.task_list_id == null ? null : String(payload.task_list_id),
44133
+ updated_at: timestamp4(payload.updated_at ?? row.updated_at)
44134
+ };
44135
+ }
44136
+
44137
+ class PostgresTodosTaskSubtreeTransferBackend {
44138
+ client;
44139
+ kind = "postgresql";
44140
+ service;
44141
+ tableName;
44142
+ tenantId;
44143
+ schemaReady = null;
44144
+ constructor(client, options = {}) {
44145
+ this.client = client;
44146
+ this.service = options.service ?? "todos";
44147
+ this.tableName = safeIdentifier3(options.tableName, DEFAULT_TODOS_POSTGRES_SYNC_TABLE);
44148
+ this.tenantId = options.tenantId ?? "default";
44149
+ }
44150
+ async ensureSchema() {
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
+ }
44166
+ await this.schemaReady;
44167
+ }
44168
+ async snapshot(client, input, forUpdate = false) {
44169
+ const lock = forUpdate ? " FOR UPDATE" : "";
44170
+ const source9 = await client.query(`SELECT object_id, payload, updated_at, version FROM ${this.tableName}
44171
+ WHERE service = $1 AND object_type = 'tasks' AND deleted_at IS NULL
44172
+ AND payload->>'project_id' = $2 ORDER BY object_id${lock}`, [this.service, input.source_project_id]);
44173
+ const allTasks = await client.query(`SELECT object_id, payload, updated_at, version FROM ${this.tableName}
44174
+ WHERE service = $1 AND object_type = 'tasks' AND deleted_at IS NULL ORDER BY object_id${lock}`, [this.service]);
44175
+ const plans = await client.query(`SELECT object_id, payload, updated_at, version FROM ${this.tableName}
44176
+ WHERE service = $1 AND object_type = 'plans' AND deleted_at IS NULL ORDER BY object_id${lock}`, [this.service]);
44177
+ const destinationProject = await client.query(`SELECT 1 FROM ${this.tableName}
44178
+ WHERE service = $1 AND object_type = 'projects' AND object_id = $2
44179
+ AND deleted_at IS NULL LIMIT 1${lock}`, [this.service, input.destination_project_id]);
44180
+ const destinationList = await client.query(`SELECT 1 FROM ${this.tableName}
44181
+ WHERE service = $1 AND object_type = 'task_lists' AND object_id = $2
44182
+ AND deleted_at IS NULL AND payload->>'project_id' = $3 LIMIT 1`, [this.service, input.destination_task_list_id, input.destination_project_id]);
44183
+ const destinationParent = input.destination_parent_id === null ? true : (await client.query(`SELECT 1 FROM ${this.tableName}
44184
+ WHERE service = $1 AND object_type = 'tasks' AND object_id = $2
44185
+ AND deleted_at IS NULL AND payload->>'project_id' = $3 LIMIT 1`, [this.service, input.destination_parent_id, input.destination_project_id])).rows.length === 1;
44186
+ return {
44187
+ source_tasks: source9.rows.map(taskRecord),
44188
+ plan_tasks: allTasks.rows.map(taskRecord),
44189
+ plans: plans.rows.map(planRecord),
44190
+ destination_project_found: destinationProject.rows.length === 1,
44191
+ destination_task_list_found: destinationList.rows.length === 1,
44192
+ destination_parent_found: destinationParent
44193
+ };
44194
+ }
44195
+ async inspect(input) {
44196
+ await this.ensureSchema();
44197
+ return prepareTransfer(await this.snapshot(this.client, input), input).inspection;
44198
+ }
44199
+ async insertPayload(client, task3, update, now4) {
44200
+ const current = await client.query(`SELECT payload FROM ${this.tableName}
44201
+ WHERE service = $1 AND object_type = 'tasks' AND object_id = $2
44202
+ AND deleted_at IS NULL FOR UPDATE`, [this.service, task3.task_id]);
44203
+ const row = current.rows[0];
44204
+ if (!row)
44205
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_NOT_FOUND", `Task not found: ${task3.task_id}`);
44206
+ const payload = payloadOf(row);
44207
+ if (Number(payload.version ?? 1) !== task3.version) {
44208
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_CAS_CONFLICT", "Task changed during transfer", { task_id: task3.task_id });
44209
+ }
44210
+ const next = {
44211
+ ...payload,
44212
+ ...update,
44213
+ version: task3.version + 1,
44214
+ updated_at: now4
44215
+ };
44216
+ const result = await client.query(`UPDATE ${this.tableName} SET payload = $1::jsonb, updated_at = $2, version = $3
44217
+ WHERE service = $4 AND object_type = 'tasks' AND object_id = $5
44218
+ AND deleted_at IS NULL AND version = $6
44219
+ RETURNING object_id`, [canonicalJson(next), now4, task3.version + 1, this.service, task3.task_id, task3.version]);
44220
+ if (result.rows.length !== 1) {
44221
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_CAS_CONFLICT", "Task CAS failed", { task_id: task3.task_id });
44222
+ }
44223
+ }
44224
+ async apply(input, options) {
44225
+ await this.ensureSchema();
44226
+ if (!this.client.transaction) {
44227
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_ATOMICITY_UNAVAILABLE", "PostgreSQL subtree transfer requires an authoritative transaction-capable client");
44228
+ }
44229
+ return this.client.transaction(async (tx) => {
44230
+ await tx.query("SELECT pg_advisory_xact_lock(hashtextextended('todos:task-parent-integrity', 0))");
44231
+ const requestDigest = taskSubtreeTransferRequestDigest(input);
44232
+ const existing = await tx.query(`SELECT result_json, request_digest, precondition_digest, idempotency_key
44233
+ FROM todos_task_subtree_transfer_receipts
44234
+ WHERE tenant_id = $1 AND kind = 'apply'
44235
+ AND (idempotency_key = $2 OR (operation_id = $3 AND step_id = $4))
44236
+ ORDER BY created_at ASC LIMIT 1`, [this.tenantId, input.idempotency_key, input.operation_id, input.step_id]);
44237
+ if (existing.rows[0]) {
44238
+ const row = existing.rows[0];
44239
+ if (row.request_digest !== requestDigest || row.precondition_digest !== input.precondition_digest || row.idempotency_key !== input.idempotency_key) {
44240
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_IDEMPOTENCY_CONFLICT", "The operation was already accepted for a different request");
44241
+ }
44242
+ return { ...typeof row.result_json === "string" ? JSON.parse(row.result_json) : row.result_json, duplicate: true };
44243
+ }
44244
+ const snapshot = await this.snapshot(tx, input, true);
44245
+ const prepared = prepareTransfer(snapshot, input);
44246
+ validateApplySnapshot(prepared, input, snapshot.plans);
44247
+ const now4 = options.now?.() ?? new Date().toISOString();
44248
+ const priorImage = {
44249
+ tasks: prepared.prior_tasks,
44250
+ plans: prepared.prior_plans
44251
+ };
44252
+ for (const task3 of prepared.prior_tasks) {
44253
+ await this.insertPayload(tx, task3, {
44254
+ project_id: input.destination_project_id,
44255
+ task_list_id: input.destination_task_list_id,
44256
+ plan_id: prepared.task_plan_targets.get(task3.task_id) ?? null,
44257
+ parent_id: task3.task_id === input.root_task_id ? input.destination_parent_id : task3.parent_id
44258
+ }, now4);
44259
+ }
44260
+ if (options.faultInjector && await options.faultInjector("after_task_writes"))
44261
+ throw new Error("Injected task-subtree-transfer fault at after_task_writes");
44262
+ for (const plan of prepared.prior_plans) {
44263
+ const row = await tx.query(`SELECT payload FROM ${this.tableName}
44264
+ WHERE service = $1 AND object_type = 'plans' AND object_id = $2
44265
+ AND deleted_at IS NULL FOR UPDATE`, [this.service, plan.plan_id]);
44266
+ const current = row.rows[0] ? payloadOf(row.rows[0]) : null;
44267
+ if (!current || String(current.project_id ?? "") !== String(plan.project_id ?? "") || (current.task_list_id ?? null) !== plan.task_list_id || timestamp4(current.updated_at ?? row.rows[0]?.updated_at) !== plan.updated_at) {
44268
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_CAS_CONFLICT", "Contained plan changed during transfer", { plan_id: plan.plan_id });
44269
+ }
44270
+ const result2 = await tx.query(`UPDATE ${this.tableName} SET payload = jsonb_set(jsonb_set(jsonb_set(payload, '{project_id}', to_jsonb($1::text)), '{task_list_id}', to_jsonb($2::text)), '{updated_at}', to_jsonb($3::text)), updated_at = $3
44271
+ WHERE service = $4 AND object_type = 'plans' AND object_id = $5 AND deleted_at IS NULL
44272
+ RETURNING object_id`, [input.destination_project_id, input.destination_task_list_id, now4, this.service, plan.plan_id]);
44273
+ if (result2.rows.length !== 1) {
44274
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_CAS_CONFLICT", "Contained plan update did not affect exactly one row", { plan_id: plan.plan_id });
44275
+ }
44276
+ }
44277
+ if (options.faultInjector && await options.faultInjector("after_plan_writes"))
44278
+ throw new Error("Injected task-subtree-transfer fault at after_plan_writes");
44279
+ const postImage = {
44280
+ tasks: prepared.prior_tasks.map((task3) => ({
44281
+ ...task3,
44282
+ project_id: input.destination_project_id,
44283
+ task_list_id: input.destination_task_list_id,
44284
+ plan_id: prepared.task_plan_targets.get(task3.task_id) ?? null,
44285
+ parent_id: task3.task_id === input.root_task_id ? input.destination_parent_id : task3.parent_id,
44286
+ version: task3.version + 1,
44287
+ updated_at: now4
44288
+ })),
44289
+ plans: prepared.prior_plans.map((plan) => ({ ...plan, project_id: input.destination_project_id, task_list_id: input.destination_task_list_id, updated_at: now4 }))
44290
+ };
44291
+ const resultDigest = canonicalDigest({ route: TODOS_TASK_SUBTREE_TRANSFER_ROUTE, request_digest: requestDigest, prior_image: priorImage, post_image: postImage });
44292
+ const receipt = {
44293
+ receipt_id: deterministicUuid(TODOS_TASK_SUBTREE_TRANSFER_ROUTE, "apply", input.operation_id, input.step_id, input.idempotency_key, requestDigest),
44294
+ authority: "todos",
44295
+ route: TODOS_TASK_SUBTREE_TRANSFER_ROUTE,
44296
+ schema_version: 1,
44297
+ kind: "apply",
44298
+ operation_id: input.operation_id,
44299
+ step_id: input.step_id,
44300
+ idempotency_key: input.idempotency_key,
44301
+ request_digest: requestDigest,
44302
+ precondition_digest: input.precondition_digest,
44303
+ result_digest: resultDigest,
44304
+ apply_receipt_id: null,
44305
+ source_project_id: input.source_project_id,
44306
+ destination_project_id: input.destination_project_id,
44307
+ destination_task_list_id: input.destination_task_list_id,
44308
+ root_task_id: input.root_task_id,
44309
+ source_population_digest: input.source_population_digest,
44310
+ prior_image: priorImage,
44311
+ post_image: postImage,
44312
+ shared_plan_splits: input.shared_plan_splits,
44313
+ created_at: now4
44314
+ };
44315
+ const result = { duplicate: false, receipt, moved_task_ids: prepared.prior_tasks.map((task3) => task3.task_id), moved_plan_ids: prepared.prior_plans.map((plan) => plan.plan_id), complete: true };
44316
+ await tx.query(`INSERT INTO todos_task_subtree_transfer_receipts (
44317
+ receipt_id, tenant_id, kind, operation_id, step_id, idempotency_key,
44318
+ request_digest, precondition_digest, result_digest, apply_receipt_id,
44319
+ request_json, result_json, created_at
44320
+ ) VALUES ($1, $2, 'apply', $3, $4, $5, $6, $7, $8, NULL, $9::jsonb, $10::jsonb, $11)`, [receipt.receipt_id, this.tenantId, receipt.operation_id, receipt.step_id, receipt.idempotency_key, receipt.request_digest, receipt.precondition_digest, receipt.result_digest, canonicalJson(input), canonicalJson(result), now4]);
44321
+ if (options.faultInjector && await options.faultInjector("after_receipt_write"))
44322
+ throw new Error("Injected task-subtree-transfer fault at after_receipt_write");
44323
+ return result;
44324
+ });
44325
+ }
44326
+ async readExact(receiptId2) {
44327
+ await this.ensureSchema();
44328
+ const row = await this.client.query(`SELECT result_json FROM todos_task_subtree_transfer_receipts WHERE tenant_id = $1 AND receipt_id = $2 LIMIT 1`, [this.tenantId, receiptId2]);
44329
+ if (!row.rows[0])
44330
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_RECEIPT_NOT_FOUND", `Transfer receipt not found: ${receiptId2}`);
44331
+ return { ...typeof row.rows[0].result_json === "string" ? JSON.parse(String(row.rows[0].result_json)) : row.rows[0].result_json, duplicate: false };
44332
+ }
44333
+ async rollback(input, options) {
44334
+ await this.ensureSchema();
44335
+ if (!this.client.transaction)
44336
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_ATOMICITY_UNAVAILABLE", "PostgreSQL subtree rollback requires a transaction-capable client");
44337
+ return this.client.transaction(async (tx) => {
44338
+ await tx.query("SELECT pg_advisory_xact_lock(hashtextextended('todos:task-parent-integrity', 0))");
44339
+ const requestDigest = taskSubtreeTransferRollbackRequestDigest(input);
44340
+ const existing = await tx.query(`SELECT result_json, request_digest, precondition_digest, idempotency_key
44341
+ FROM todos_task_subtree_transfer_receipts
44342
+ WHERE tenant_id = $1 AND kind = 'rollback'
44343
+ AND (idempotency_key = $2 OR (operation_id = $3 AND step_id = $4))
44344
+ ORDER BY created_at ASC LIMIT 1`, [this.tenantId, input.idempotency_key, input.operation_id, input.step_id]);
44345
+ if (existing.rows[0]) {
44346
+ const row = existing.rows[0];
44347
+ if (row.request_digest !== requestDigest || row.precondition_digest !== input.precondition_digest || row.idempotency_key !== input.idempotency_key)
44348
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_IDEMPOTENCY_CONFLICT", "The rollback operation was already accepted for a different request");
44349
+ return { ...typeof row.result_json === "string" ? JSON.parse(String(row.result_json)) : row.result_json, duplicate: true };
44350
+ }
44351
+ const applyRow = await tx.query(`SELECT result_json FROM todos_task_subtree_transfer_receipts WHERE tenant_id = $1 AND kind = 'apply' AND receipt_id = $2 LIMIT 1`, [this.tenantId, input.receipt_id]);
44352
+ if (!applyRow.rows[0])
44353
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_RECEIPT_NOT_FOUND", `Apply receipt not found: ${input.receipt_id}`);
44354
+ const applied = typeof applyRow.rows[0].result_json === "string" ? JSON.parse(String(applyRow.rows[0].result_json)) : applyRow.rows[0].result_json;
44355
+ if (applied.receipt.operation_id !== input.operation_id) {
44356
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_ROLLBACK_CONFLICT", "Rollback operation does not match the apply receipt");
44357
+ }
44358
+ const expectedPrecondition = canonicalDigest({ route: TODOS_TASK_SUBTREE_TRANSFER_ROUTE, direction: "rollback", operation_id: input.operation_id, step_id: input.step_id, apply_receipt_id: input.receipt_id, apply_result_digest: applied.receipt.result_digest });
44359
+ if (input.precondition_digest !== expectedPrecondition)
44360
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_DIGEST_MISMATCH", "Rollback precondition digest does not match the exact apply receipt", { expected_precondition_digest: expectedPrecondition });
44361
+ const now4 = options.now?.() ?? new Date().toISOString();
44362
+ for (const task3 of applied.receipt.post_image.tasks) {
44363
+ const row = await tx.query(`SELECT payload FROM ${this.tableName} WHERE service = $1 AND object_type = 'tasks' AND object_id = $2 AND deleted_at IS NULL FOR UPDATE`, [this.service, task3.task_id]);
44364
+ if (!row.rows[0])
44365
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_ROLLBACK_CONFLICT", "Transferred task disappeared", { task_id: task3.task_id });
44366
+ const payload = payloadOf(row.rows[0]);
44367
+ if (Number(payload.version ?? 1) !== task3.version || String(payload.project_id ?? "") !== String(task3.project_id ?? "") || (payload.parent_id ?? null) !== task3.parent_id || (payload.plan_id ?? null) !== task3.plan_id || (payload.task_list_id ?? null) !== task3.task_list_id)
44368
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_ROLLBACK_CONFLICT", "Transferred task changed after apply", { task_id: task3.task_id });
44369
+ }
44370
+ for (const task3 of applied.receipt.prior_image.tasks) {
44371
+ const row = await tx.query(`SELECT payload FROM ${this.tableName} WHERE service = $1 AND object_type = 'tasks' AND object_id = $2 AND deleted_at IS NULL`, [this.service, task3.task_id]);
44372
+ const payload = row.rows[0] ? payloadOf(row.rows[0]) : null;
44373
+ if (!payload)
44374
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_ROLLBACK_CONFLICT", "Task disappeared during rollback", { task_id: task3.task_id });
44375
+ const next = { ...payload, project_id: task3.project_id, task_list_id: task3.task_list_id, plan_id: task3.plan_id, parent_id: task3.parent_id, version: task3.version + 2, updated_at: now4 };
44376
+ const result2 = await tx.query(`UPDATE ${this.tableName} SET payload = $1::jsonb, updated_at = $2, version = $3
44377
+ WHERE service = $4 AND object_type = 'tasks' AND object_id = $5 AND deleted_at IS NULL AND version = $6
44378
+ RETURNING object_id`, [canonicalJson(next), now4, task3.version + 2, this.service, task3.task_id, task3.version + 1]);
44379
+ if (result2.rows.length !== 1) {
44380
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_ROLLBACK_CONFLICT", "Task rollback did not affect exactly one row", { task_id: task3.task_id });
44381
+ }
44382
+ }
44383
+ if (options.faultInjector && await options.faultInjector("after_rollback_task_writes"))
44384
+ throw new Error("Injected task-subtree-transfer fault at after_rollback_task_writes");
44385
+ for (const plan of applied.receipt.prior_image.plans) {
44386
+ const post = applied.receipt.post_image.plans.find((candidate) => candidate.plan_id === plan.plan_id);
44387
+ const result2 = await tx.query(`UPDATE ${this.tableName}
44388
+ SET payload = jsonb_set(
44389
+ jsonb_set(
44390
+ jsonb_set(payload, '{project_id}', to_jsonb($1::text)),
44391
+ '{task_list_id}',
44392
+ COALESCE(to_jsonb($2::text), 'null'::jsonb)
44393
+ ),
44394
+ '{updated_at}',
44395
+ to_jsonb($3::text)
44396
+ ), updated_at = $3
44397
+ WHERE service = $4 AND object_type = 'plans' AND object_id = $5 AND deleted_at IS NULL
44398
+ AND payload->>'project_id' = $6 AND COALESCE(payload->>'task_list_id', '') = COALESCE($7, '') AND payload->>'updated_at' = $8
44399
+ RETURNING object_id`, [plan.project_id, plan.task_list_id, now4, this.service, plan.plan_id, post.project_id, post.task_list_id, post.updated_at]);
44400
+ if (result2.rows.length !== 1) {
44401
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_ROLLBACK_CONFLICT", "Contained plan rollback did not affect exactly one row", { plan_id: plan.plan_id });
44402
+ }
44403
+ }
44404
+ if (options.faultInjector && await options.faultInjector("after_rollback_plan_writes"))
44405
+ throw new Error("Injected task-subtree-transfer fault at after_rollback_plan_writes");
44406
+ const restored = {
44407
+ tasks: applied.receipt.prior_image.tasks.map((task3) => ({ ...task3, version: task3.version + 2, updated_at: now4 })),
44408
+ plans: applied.receipt.prior_image.plans.map((plan) => ({ ...plan, updated_at: now4 }))
44409
+ };
44410
+ const resultDigest = canonicalDigest({ route: TODOS_TASK_SUBTREE_TRANSFER_ROUTE, direction: "rollback", apply_receipt_id: input.receipt_id, prior_image: applied.receipt.post_image, post_image: restored });
44411
+ const receipt = { ...applied.receipt, receipt_id: deterministicUuid(TODOS_TASK_SUBTREE_TRANSFER_ROUTE, "rollback", input.operation_id, input.step_id, input.idempotency_key, input.receipt_id), kind: "rollback", operation_id: input.operation_id, step_id: input.step_id, idempotency_key: input.idempotency_key, request_digest: requestDigest, precondition_digest: input.precondition_digest, result_digest: resultDigest, apply_receipt_id: input.receipt_id, prior_image: applied.receipt.post_image, post_image: restored, created_at: now4 };
44412
+ const result = { duplicate: false, receipt, moved_task_ids: applied.moved_task_ids, moved_plan_ids: applied.moved_plan_ids, complete: true };
44413
+ await tx.query(`INSERT INTO todos_task_subtree_transfer_receipts (
44414
+ receipt_id, tenant_id, kind, operation_id, step_id, idempotency_key,
44415
+ request_digest, precondition_digest, result_digest, apply_receipt_id,
44416
+ request_json, result_json, created_at
44417
+ ) VALUES ($1, $2, 'rollback', $3, $4, $5, $6, $7, $8, $9, $10::jsonb, $11::jsonb, $12)`, [receipt.receipt_id, this.tenantId, receipt.operation_id, receipt.step_id, receipt.idempotency_key, receipt.request_digest, receipt.precondition_digest, receipt.result_digest, receipt.apply_receipt_id, canonicalJson(input), canonicalJson(result), now4]);
44418
+ if (options.faultInjector && await options.faultInjector("after_rollback_receipt_write"))
44419
+ throw new Error("Injected task-subtree-transfer fault at after_rollback_receipt_write");
44420
+ return result;
44421
+ });
44422
+ }
44423
+ }
44424
+
44425
+ // src/task-subtree-transfer/sqlite.ts
44426
+ var sqliteTails2 = new WeakMap;
44427
+ async function fault3(options, point) {
44428
+ if (options.faultInjector && await options.faultInjector(point) === true) {
44429
+ throw new Error(`Injected task-subtree-transfer fault at ${point}`);
44430
+ }
44431
+ }
44432
+ function parseResult(value, duplicate) {
44433
+ return { ...JSON.parse(value), duplicate };
44434
+ }
44435
+ function rowImage(row) {
44436
+ return {
44437
+ task_id: String(row.id),
44438
+ project_id: row.project_id == null ? null : String(row.project_id),
44439
+ parent_id: row.parent_id == null ? null : String(row.parent_id),
44440
+ plan_id: row.plan_id == null ? null : String(row.plan_id),
44441
+ task_list_id: row.task_list_id == null ? null : String(row.task_list_id),
44442
+ version: Number(row.version),
44443
+ updated_at: String(row.updated_at),
44444
+ archived_at: row.archived_at == null ? null : String(row.archived_at)
44445
+ };
44446
+ }
44447
+ function planImage(row) {
44448
+ return {
44449
+ plan_id: String(row.id),
44450
+ project_id: row.project_id == null ? null : String(row.project_id),
44451
+ task_list_id: row.task_list_id == null ? null : String(row.task_list_id),
44452
+ updated_at: String(row.updated_at)
44453
+ };
44454
+ }
44455
+ function imageFromTaskRow(row) {
44456
+ const image = rowImage(row);
44457
+ const { archived_at: _archivedAt, ...withoutArchive } = image;
44458
+ return withoutArchive;
44459
+ }
44460
+ function imageFromPlanRow(row) {
44461
+ return planImage(row);
44462
+ }
44463
+ function receiptId2(input) {
44464
+ return deterministicUuid(TODOS_TASK_SUBTREE_TRANSFER_ROUTE, "apply", input.operation_id, input.step_id, input.idempotency_key, taskSubtreeTransferRequestDigest(input));
44465
+ }
44466
+ function rollbackReceiptId(input) {
44467
+ return deterministicUuid(TODOS_TASK_SUBTREE_TRANSFER_ROUTE, "rollback", input.operation_id, input.step_id, input.idempotency_key, input.receipt_id);
44468
+ }
44469
+ function taskWhere(db, taskId) {
44470
+ const row = db.query(`SELECT id, project_id, parent_id, plan_id, task_list_id, version, updated_at, archived_at
44471
+ FROM tasks WHERE id = ? LIMIT 1`).get(taskId);
44472
+ if (!row) {
44473
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_NOT_FOUND", `Task not found: ${taskId}`, { task_id: taskId });
44474
+ }
44475
+ return row;
44476
+ }
44477
+
44478
+ class SqliteTodosTaskSubtreeTransferBackend {
44479
+ db;
44480
+ tenantId;
44481
+ kind = "sqlite";
44482
+ constructor(db, tenantId = "default") {
44483
+ this.db = db;
44484
+ this.tenantId = tenantId;
44485
+ ensureSqliteTodosTaskSubtreeTransferSchema(db);
44486
+ }
44487
+ async serialized(run) {
44488
+ const previous = sqliteTails2.get(this.db) ?? Promise.resolve();
44489
+ let release;
44490
+ const current = new Promise((resolve10) => {
44491
+ release = resolve10;
44492
+ });
44493
+ const tail = previous.then(() => current);
44494
+ sqliteTails2.set(this.db, tail);
44495
+ await previous;
44496
+ try {
44497
+ this.db.exec("BEGIN IMMEDIATE");
44498
+ try {
44499
+ const result = await run();
44500
+ this.db.exec("COMMIT");
44501
+ return result;
44502
+ } catch (error) {
44503
+ this.db.exec("ROLLBACK");
44504
+ throw error;
44505
+ }
44506
+ } finally {
44507
+ release();
44508
+ if (sqliteTails2.get(this.db) === tail)
44509
+ sqliteTails2.delete(this.db);
44510
+ }
44511
+ }
44512
+ snapshot(input) {
44513
+ const sourceTasks = this.db.query(`SELECT id, project_id, parent_id, plan_id, task_list_id, version, updated_at, archived_at
44514
+ FROM tasks WHERE project_id = ? ORDER BY id`).all(input.source_project_id).map(rowImage);
44515
+ const allTasks = this.db.query(`SELECT id, project_id, parent_id, plan_id, task_list_id, version, updated_at, archived_at
44516
+ FROM tasks ORDER BY id`).all().map(rowImage);
44517
+ const plans = this.db.query(`SELECT id, project_id, task_list_id, updated_at
44518
+ FROM plans ORDER BY id`).all().map(planImage);
44519
+ const destinationProjectFound = Boolean(this.db.query("SELECT id FROM projects WHERE id = ? LIMIT 1").get(input.destination_project_id));
44520
+ const destinationTaskList = this.db.query("SELECT id FROM task_lists WHERE id = ? AND project_id = ? LIMIT 1").get(input.destination_task_list_id, input.destination_project_id);
44521
+ const destinationParentFound = input.destination_parent_id === null || Boolean(this.db.query("SELECT id FROM tasks WHERE id = ? AND project_id = ? LIMIT 1").get(input.destination_parent_id, input.destination_project_id));
44522
+ return {
44523
+ source_tasks: sourceTasks,
44524
+ plan_tasks: allTasks,
44525
+ plans,
44526
+ destination_project_found: destinationProjectFound,
44527
+ destination_task_list_found: Boolean(destinationTaskList),
44528
+ destination_parent_found: destinationParentFound
44529
+ };
44530
+ }
44531
+ async inspect(input) {
44532
+ return this.serialized(() => prepareTransfer(this.snapshot(input), input).inspection);
44533
+ }
44534
+ async apply(input, options) {
44535
+ return this.serialized(async () => {
44536
+ const requestDigest = taskSubtreeTransferRequestDigest(input);
44537
+ const existing = this.db.query(`SELECT result_json, request_digest, precondition_digest, idempotency_key
44538
+ FROM todos_task_subtree_transfer_receipts
44539
+ WHERE tenant_id = ? AND kind = 'apply'
44540
+ AND (idempotency_key = ? OR (operation_id = ? AND step_id = ?))
44541
+ ORDER BY created_at ASC LIMIT 1`).get(this.tenantId, input.idempotency_key, input.operation_id, input.step_id);
44542
+ if (existing) {
44543
+ if (existing.request_digest !== requestDigest || existing.precondition_digest !== input.precondition_digest || existing.idempotency_key !== input.idempotency_key) {
44544
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_IDEMPOTENCY_CONFLICT", "The operation/step or idempotency key was already accepted for a different request");
44545
+ }
44546
+ return parseResult(String(existing.result_json), true);
44547
+ }
44548
+ const snapshot = this.snapshot(input);
44549
+ const prepared = prepareTransfer(snapshot, input);
44550
+ validateApplySnapshot(prepared, input, snapshot.plans);
44551
+ const priorImage = {
44552
+ tasks: prepared.prior_tasks,
44553
+ plans: prepared.prior_plans
44554
+ };
44555
+ const now4 = options.now?.() ?? new Date().toISOString();
44556
+ const movedTaskIds = prepared.prior_tasks.map((task3) => task3.task_id);
44557
+ for (const task3 of prepared.prior_tasks) {
44558
+ const current = imageFromTaskRow(taskWhere(this.db, task3.task_id));
44559
+ if (canonicalJson(current) !== canonicalJson(task3)) {
44560
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_CAS_CONFLICT", "A task changed after inspection", { task_id: task3.task_id });
44561
+ }
44562
+ }
44563
+ for (const task3 of prepared.prior_tasks) {
44564
+ const targetPlan = prepared.task_plan_targets.get(task3.task_id) ?? null;
44565
+ const parentId = task3.task_id === input.root_task_id ? input.destination_parent_id : task3.parent_id;
44566
+ const result2 = this.db.query(`UPDATE tasks
44567
+ SET project_id = ?, task_list_id = ?, plan_id = ?, parent_id = ?, version = version + 1, updated_at = ?
44568
+ WHERE id = ? AND version = ?`).run(input.destination_project_id, input.destination_task_list_id, targetPlan, parentId, now4, task3.task_id, task3.version);
44569
+ if (result2.changes !== 1) {
44570
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_CAS_CONFLICT", "A task version changed during the atomic transfer", { task_id: task3.task_id });
44571
+ }
44572
+ }
44573
+ await fault3(options, "after_task_writes");
44574
+ for (const plan of prepared.prior_plans) {
44575
+ const result2 = this.db.query(`UPDATE plans SET project_id = ?, task_list_id = ?, updated_at = ?
44576
+ WHERE id = ? AND project_id IS ? AND task_list_id IS ? AND updated_at = ?`).run(input.destination_project_id, input.destination_task_list_id, now4, plan.plan_id, plan.project_id, plan.task_list_id, plan.updated_at);
44577
+ if (result2.changes !== 1) {
44578
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_CAS_CONFLICT", "A contained plan changed during the atomic transfer", { plan_id: plan.plan_id });
44579
+ }
44580
+ }
44581
+ await fault3(options, "after_plan_writes");
44582
+ const postImage = {
44583
+ tasks: prepared.prior_tasks.map((task3) => ({
44584
+ ...task3,
44585
+ project_id: input.destination_project_id,
44586
+ task_list_id: input.destination_task_list_id,
44587
+ plan_id: prepared.task_plan_targets.get(task3.task_id) ?? null,
44588
+ parent_id: task3.task_id === input.root_task_id ? input.destination_parent_id : task3.parent_id,
44589
+ version: task3.version + 1,
44590
+ updated_at: now4
44591
+ })),
44592
+ plans: prepared.prior_plans.map((plan) => ({
44593
+ ...plan,
44594
+ project_id: input.destination_project_id,
44595
+ task_list_id: input.destination_task_list_id,
44596
+ updated_at: now4
44597
+ }))
44598
+ };
44599
+ const resultDigest = canonicalDigest({
44600
+ route: TODOS_TASK_SUBTREE_TRANSFER_ROUTE,
44601
+ request_digest: requestDigest,
44602
+ prior_image: priorImage,
44603
+ post_image: postImage
44604
+ });
44605
+ const receipt = {
44606
+ receipt_id: receiptId2(input),
44607
+ authority: "todos",
44608
+ route: TODOS_TASK_SUBTREE_TRANSFER_ROUTE,
44609
+ schema_version: 1,
44610
+ kind: "apply",
44611
+ operation_id: input.operation_id,
44612
+ step_id: input.step_id,
44613
+ idempotency_key: input.idempotency_key,
44614
+ request_digest: requestDigest,
44615
+ precondition_digest: input.precondition_digest,
44616
+ result_digest: resultDigest,
44617
+ apply_receipt_id: null,
44618
+ source_project_id: input.source_project_id,
44619
+ destination_project_id: input.destination_project_id,
44620
+ destination_task_list_id: input.destination_task_list_id,
44621
+ root_task_id: input.root_task_id,
44622
+ source_population_digest: input.source_population_digest,
44623
+ prior_image: priorImage,
44624
+ post_image: postImage,
44625
+ shared_plan_splits: input.shared_plan_splits,
44626
+ created_at: now4
44627
+ };
44628
+ const result = {
44629
+ duplicate: false,
44630
+ receipt,
44631
+ moved_task_ids: movedTaskIds,
44632
+ moved_plan_ids: prepared.prior_plans.map((plan) => plan.plan_id),
44633
+ complete: true
44634
+ };
44635
+ this.db.query(`INSERT INTO todos_task_subtree_transfer_receipts (
44636
+ receipt_id, tenant_id, kind, operation_id, step_id, idempotency_key,
44637
+ request_digest, precondition_digest, result_digest, apply_receipt_id,
44638
+ request_json, result_json, created_at
44639
+ ) VALUES (?, ?, 'apply', ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?)`).run(receipt.receipt_id, this.tenantId, receipt.operation_id, receipt.step_id, receipt.idempotency_key, receipt.request_digest, receipt.precondition_digest, receipt.result_digest, canonicalJson(input), canonicalJson(result), now4);
44640
+ await fault3(options, "after_receipt_write");
44641
+ return result;
44642
+ });
44643
+ }
44644
+ async readExact(receiptIdValue) {
44645
+ const row = this.db.query(`SELECT result_json FROM todos_task_subtree_transfer_receipts
44646
+ WHERE tenant_id = ? AND receipt_id = ? LIMIT 1`).get(this.tenantId, receiptIdValue);
44647
+ if (!row) {
44648
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_RECEIPT_NOT_FOUND", `Transfer receipt not found: ${receiptIdValue}`);
44649
+ }
44650
+ return parseResult(String(row.result_json), false);
44651
+ }
44652
+ async rollback(input, options) {
44653
+ return this.serialized(async () => {
44654
+ const requestDigest = taskSubtreeTransferRollbackRequestDigest(input);
44655
+ const existing = this.db.query(`SELECT result_json, request_digest, precondition_digest, idempotency_key
44656
+ FROM todos_task_subtree_transfer_receipts
44657
+ WHERE tenant_id = ? AND kind = 'rollback'
44658
+ AND (idempotency_key = ? OR (operation_id = ? AND step_id = ?))
44659
+ ORDER BY created_at ASC LIMIT 1`).get(this.tenantId, input.idempotency_key, input.operation_id, input.step_id);
44660
+ if (existing) {
44661
+ if (existing.request_digest !== requestDigest || existing.precondition_digest !== input.precondition_digest || existing.idempotency_key !== input.idempotency_key) {
44662
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_IDEMPOTENCY_CONFLICT", "The rollback operation/step or idempotency key was already accepted for a different request");
44663
+ }
44664
+ return parseResult(String(existing.result_json), true);
44665
+ }
44666
+ const applyRow = this.db.query(`SELECT result_json FROM todos_task_subtree_transfer_receipts
44667
+ WHERE tenant_id = ? AND kind = 'apply' AND receipt_id = ? LIMIT 1`).get(this.tenantId, input.receipt_id);
44668
+ if (!applyRow) {
44669
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_RECEIPT_NOT_FOUND", `Apply receipt not found: ${input.receipt_id}`);
44670
+ }
44671
+ const applied = parseResult(String(applyRow.result_json), false);
44672
+ if (applied.receipt.operation_id !== input.operation_id) {
44673
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_ROLLBACK_CONFLICT", "Rollback operation does not match the apply receipt");
44674
+ }
44675
+ const expectedPrecondition = canonicalDigest({
44676
+ route: TODOS_TASK_SUBTREE_TRANSFER_ROUTE,
44677
+ direction: "rollback",
44678
+ operation_id: input.operation_id,
44679
+ step_id: input.step_id,
44680
+ apply_receipt_id: input.receipt_id,
44681
+ apply_result_digest: applied.receipt.result_digest
44682
+ });
44683
+ if (input.precondition_digest !== expectedPrecondition) {
44684
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_DIGEST_MISMATCH", "Rollback precondition digest does not match the exact apply receipt", { expected_precondition_digest: expectedPrecondition });
44685
+ }
44686
+ const now4 = options.now?.() ?? new Date().toISOString();
44687
+ for (const task3 of applied.receipt.post_image.tasks) {
44688
+ const current = imageFromTaskRow(taskWhere(this.db, task3.task_id));
44689
+ if (canonicalJson(current) !== canonicalJson(task3)) {
44690
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_ROLLBACK_CONFLICT", "Rollback refused because a transferred task changed after apply", { task_id: task3.task_id });
44691
+ }
44692
+ }
44693
+ for (const plan of applied.receipt.post_image.plans) {
44694
+ const row = this.db.query("SELECT id, project_id, task_list_id, updated_at FROM plans WHERE id = ? LIMIT 1").get(plan.plan_id);
44695
+ if (!row || canonicalJson(imageFromPlanRow(row)) !== canonicalJson(plan)) {
44696
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_ROLLBACK_CONFLICT", "Rollback refused because a transferred plan changed after apply", { plan_id: plan.plan_id });
44697
+ }
44698
+ }
44699
+ for (const task3 of applied.receipt.prior_image.tasks) {
44700
+ const result2 = this.db.query(`UPDATE tasks SET project_id = ?, task_list_id = ?, plan_id = ?, parent_id = ?,
44701
+ version = version + 1, updated_at = ?
44702
+ WHERE id = ? AND version = ?`).run(task3.project_id, task3.task_list_id, task3.plan_id, task3.parent_id, now4, task3.task_id, applied.receipt.post_image.tasks.find((candidate) => candidate.task_id === task3.task_id).version);
44703
+ if (result2.changes !== 1) {
44704
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_ROLLBACK_CONFLICT", "Rollback task CAS failed", { task_id: task3.task_id });
44705
+ }
44706
+ }
44707
+ await fault3(options, "after_rollback_task_writes");
44708
+ for (const plan of applied.receipt.prior_image.plans) {
44709
+ const post = applied.receipt.post_image.plans.find((candidate) => candidate.plan_id === plan.plan_id);
44710
+ const result2 = this.db.query(`UPDATE plans SET project_id = ?, task_list_id = ?, updated_at = ?
44711
+ WHERE id = ? AND project_id IS ? AND task_list_id IS ? AND updated_at = ?`).run(plan.project_id, plan.task_list_id, now4, plan.plan_id, post.project_id, post.task_list_id, post.updated_at);
44712
+ if (result2.changes !== 1) {
44713
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_ROLLBACK_CONFLICT", "Rollback plan CAS failed", { plan_id: plan.plan_id });
44714
+ }
44715
+ }
44716
+ await fault3(options, "after_rollback_plan_writes");
44717
+ const restored = {
44718
+ tasks: applied.receipt.prior_image.tasks.map((task3) => ({
44719
+ ...task3,
44720
+ version: applied.receipt.post_image.tasks.find((candidate) => candidate.task_id === task3.task_id).version + 1,
44721
+ updated_at: now4
44722
+ })),
44723
+ plans: applied.receipt.prior_image.plans.map((plan) => ({ ...plan, updated_at: now4 }))
44724
+ };
44725
+ const resultDigest = canonicalDigest({
44726
+ route: TODOS_TASK_SUBTREE_TRANSFER_ROUTE,
44727
+ direction: "rollback",
44728
+ apply_receipt_id: input.receipt_id,
44729
+ prior_image: applied.receipt.post_image,
44730
+ post_image: restored
44731
+ });
44732
+ const receipt = {
44733
+ ...applied.receipt,
44734
+ receipt_id: rollbackReceiptId(input),
44735
+ kind: "rollback",
44736
+ step_id: input.step_id,
44737
+ idempotency_key: input.idempotency_key,
44738
+ request_digest: requestDigest,
44739
+ precondition_digest: input.precondition_digest,
44740
+ result_digest: resultDigest,
44741
+ apply_receipt_id: input.receipt_id,
44742
+ prior_image: applied.receipt.post_image,
44743
+ post_image: restored,
44744
+ created_at: now4
44745
+ };
44746
+ const result = {
44747
+ duplicate: false,
44748
+ receipt,
44749
+ moved_task_ids: applied.moved_task_ids,
44750
+ moved_plan_ids: applied.moved_plan_ids,
44751
+ complete: true
44752
+ };
44753
+ this.db.query(`INSERT INTO todos_task_subtree_transfer_receipts (
44754
+ receipt_id, tenant_id, kind, operation_id, step_id, idempotency_key,
44755
+ request_digest, precondition_digest, result_digest, apply_receipt_id,
44756
+ request_json, result_json, created_at
44757
+ ) VALUES (?, ?, 'rollback', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(receipt.receipt_id, this.tenantId, receipt.operation_id, receipt.step_id, receipt.idempotency_key, receipt.request_digest, receipt.precondition_digest, receipt.result_digest, receipt.apply_receipt_id, canonicalJson(input), canonicalJson(result), now4);
44758
+ await fault3(options, "after_rollback_receipt_write");
44759
+ return result;
44760
+ });
44761
+ }
44762
+ }
44763
+
44764
+ // src/task-subtree-transfer/authority.ts
44765
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
44766
+ var DIGEST = /^[0-9a-f]{64}$/i;
44767
+ function boundedString(value, field2, max = 200) {
44768
+ if (typeof value !== "string" || value.length === 0 || value.length > max) {
44769
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", `${field2} must be a non-empty bounded string`);
44770
+ }
44771
+ return value;
44772
+ }
44773
+ function uuid3(value, field2) {
44774
+ const parsed = boundedString(value, field2, 80);
44775
+ if (!UUID.test(parsed)) {
44776
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", `${field2} must be a UUID`);
44777
+ }
44778
+ return parsed;
44779
+ }
44780
+ function digest2(value, field2) {
44781
+ const parsed = boundedString(value, field2, 64);
44782
+ if (!DIGEST.test(parsed)) {
44783
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", `${field2} must be a SHA-256 digest`);
44784
+ }
44785
+ return parsed.toLowerCase();
44786
+ }
44787
+ function nullableUuid(value, field2) {
44788
+ if (value === null)
44789
+ return null;
44790
+ return uuid3(value, field2);
44791
+ }
44792
+ function strictObject(input, fields) {
44793
+ if (input === null || typeof input !== "object" || Array.isArray(input)) {
44794
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "Request must be a JSON object");
44795
+ }
44796
+ const object = input;
44797
+ const allowed = new Set(fields);
44798
+ for (const key2 of Object.keys(object)) {
44799
+ if (!allowed.has(key2)) {
44800
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", `Unknown field: ${key2}`);
44801
+ }
44802
+ }
44803
+ return object;
44804
+ }
44805
+ var INSPECT_FIELDS = [
44806
+ "source_project_id",
44807
+ "destination_project_id",
44808
+ "destination_task_list_id",
44809
+ "root_task_id",
44810
+ "destination_parent_id"
44811
+ ];
44812
+ function parseTodosTaskSubtreeTransferInspect(input) {
44813
+ const value = strictObject(input, INSPECT_FIELDS);
44814
+ const request = {
44815
+ source_project_id: uuid3(value.source_project_id, "source_project_id"),
44816
+ destination_project_id: uuid3(value.destination_project_id, "destination_project_id"),
44817
+ destination_task_list_id: uuid3(value.destination_task_list_id, "destination_task_list_id"),
44818
+ root_task_id: uuid3(value.root_task_id, "root_task_id"),
44819
+ destination_parent_id: nullableUuid(value.destination_parent_id, "destination_parent_id")
44820
+ };
44821
+ if (request.source_project_id === request.destination_project_id) {
44822
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "source_project_id and destination_project_id must identify different Projects");
44823
+ }
44824
+ return request;
44825
+ }
44826
+ var APPLY_FIELDS = [
44827
+ ...INSPECT_FIELDS,
44828
+ "version",
44829
+ "operation_id",
44830
+ "step_id",
44831
+ "idempotency_key",
44832
+ "precondition_digest",
44833
+ "expected_root_parent_id",
44834
+ "source_population_digest",
44835
+ "expected_tasks",
44836
+ "shared_plan_splits"
44837
+ ];
44838
+ function parseTodosTaskSubtreeTransferApply(input) {
44839
+ const value = strictObject(input, APPLY_FIELDS);
44840
+ if (value.version !== 1) {
44841
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "version must be 1");
44842
+ }
44843
+ if (!Array.isArray(value.expected_tasks) || value.expected_tasks.length === 0) {
44844
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "expected_tasks must be a non-empty array");
44845
+ }
44846
+ const expected_tasks = value.expected_tasks.map((item, index) => {
44847
+ const row = strictObject(item, ["task_id", "version"]);
44848
+ const version = row.version;
44849
+ if (!Number.isSafeInteger(version) || Number(version) < 1) {
44850
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", `expected_tasks.${index}.version must be a positive integer`);
44851
+ }
44852
+ return { task_id: uuid3(row.task_id, `expected_tasks.${index}.task_id`), version: Number(version) };
44853
+ });
44854
+ const ids2 = new Set;
44855
+ for (const row of expected_tasks) {
44856
+ if (ids2.has(row.task_id)) {
44857
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "expected_tasks must not contain duplicate task IDs");
44858
+ }
44859
+ ids2.add(row.task_id);
44860
+ }
44861
+ if (!Array.isArray(value.shared_plan_splits)) {
44862
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "shared_plan_splits must be an array");
44863
+ }
44864
+ const shared_plan_splits = value.shared_plan_splits.map((item, index) => {
44865
+ const row = strictObject(item, ["source_plan_id", "destination_plan_id"]);
44866
+ return {
44867
+ source_plan_id: uuid3(row.source_plan_id, `shared_plan_splits.${index}.source_plan_id`),
44868
+ destination_plan_id: uuid3(row.destination_plan_id, `shared_plan_splits.${index}.destination_plan_id`)
44869
+ };
44870
+ });
44871
+ return {
44872
+ ...parseTodosTaskSubtreeTransferInspect({
44873
+ source_project_id: value.source_project_id,
44874
+ destination_project_id: value.destination_project_id,
44875
+ destination_task_list_id: value.destination_task_list_id,
44876
+ root_task_id: value.root_task_id,
44877
+ destination_parent_id: value.destination_parent_id
44878
+ }),
44879
+ version: 1,
44880
+ operation_id: boundedString(value.operation_id, "operation_id"),
44881
+ step_id: boundedString(value.step_id, "step_id"),
44882
+ idempotency_key: boundedString(value.idempotency_key, "idempotency_key", 240),
44883
+ precondition_digest: digest2(value.precondition_digest, "precondition_digest"),
44884
+ expected_root_parent_id: nullableUuid(value.expected_root_parent_id, "expected_root_parent_id"),
44885
+ source_population_digest: digest2(value.source_population_digest, "source_population_digest"),
44886
+ expected_tasks,
44887
+ shared_plan_splits
44888
+ };
44889
+ }
44890
+ function parseTodosTaskSubtreeTransferRollback(input) {
44891
+ const value = strictObject(input, [
44892
+ "receipt_id",
44893
+ "operation_id",
44894
+ "step_id",
44895
+ "idempotency_key",
44896
+ "precondition_digest"
44897
+ ]);
44898
+ return {
44899
+ receipt_id: uuid3(value.receipt_id, "receipt_id"),
44900
+ operation_id: boundedString(value.operation_id, "operation_id"),
44901
+ step_id: boundedString(value.step_id, "step_id"),
44902
+ idempotency_key: boundedString(value.idempotency_key, "idempotency_key", 240),
44903
+ precondition_digest: digest2(value.precondition_digest, "precondition_digest")
44904
+ };
44905
+ }
44906
+ function deriveTodosTaskSubtreeTransferApplyPreconditionDigest(input) {
44907
+ const {
44908
+ source_project_id,
44909
+ destination_project_id,
44910
+ destination_task_list_id,
44911
+ root_task_id,
44912
+ expected_root_parent_id,
44913
+ destination_parent_id,
44914
+ source_population_digest,
44915
+ expected_tasks,
44916
+ shared_plan_splits
44917
+ } = input;
44918
+ return canonicalDigest({
44919
+ route: TODOS_TASK_SUBTREE_TRANSFER_ROUTE,
44920
+ direction: "apply",
44921
+ source_project_id,
44922
+ destination_project_id,
44923
+ destination_task_list_id,
44924
+ root_task_id,
44925
+ expected_root_parent_id,
44926
+ destination_parent_id,
44927
+ source_population_digest,
44928
+ expected_tasks,
44929
+ shared_plan_splits
44930
+ });
44931
+ }
44932
+ function deriveTodosTaskSubtreeTransferRollbackPreconditionDigest(input) {
44933
+ return canonicalDigest({
44934
+ route: TODOS_TASK_SUBTREE_TRANSFER_ROUTE,
44935
+ direction: "rollback",
44936
+ apply_receipt_id: input.receipt_id,
44937
+ operation_id: input.operation_id,
44938
+ step_id: input.step_id,
44939
+ apply_result_digest: input.apply_result_digest
44940
+ });
44941
+ }
44942
+ function resolveTenantId2(value) {
44943
+ const tenant = value ?? "default";
44944
+ if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/.test(tenant)) {
44945
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "tenantId must be a bounded exact authority identifier");
44946
+ }
44947
+ return tenant;
44948
+ }
44949
+
44950
+ class PackageOwnedTodosTaskSubtreeTransferAuthority {
44951
+ backend;
44952
+ tenantId;
44953
+ backendOptions;
44954
+ constructor(backend, options = {}) {
44955
+ this.backend = backend;
44956
+ this.tenantId = resolveTenantId2(options.tenantId);
44957
+ this.backendOptions = { ...options, tenantId: this.tenantId };
44958
+ }
44959
+ async capability() {
44960
+ return {
44961
+ authority: "todos",
44962
+ route: TODOS_TASK_SUBTREE_TRANSFER_ROUTE,
44963
+ schema_version: TODOS_TASK_SUBTREE_TRANSFER_SCHEMA_VERSION,
44964
+ tenant_id: this.tenantId,
44965
+ backend: this.backend.kind,
44966
+ exact_descendant_closure: true,
44967
+ complete_source_population_digest: true,
44968
+ per_task_version_cas: true,
44969
+ explicit_shared_plan_splits: true,
44970
+ atomic_apply: true,
44971
+ immutable_forward_inverse_receipts: true,
44972
+ prior_image_receipts: true,
44973
+ cas_protected_rollback: true,
44974
+ preserves_descendant_parent_ids: true,
44975
+ preserves_task_and_relation_identities: true
44976
+ };
44977
+ }
44978
+ async inspect(input) {
44979
+ return this.backend.inspect(parseTodosTaskSubtreeTransferInspect(input));
44980
+ }
44981
+ async apply(input) {
44982
+ const request = parseTodosTaskSubtreeTransferApply(input);
44983
+ const expected = deriveTodosTaskSubtreeTransferApplyPreconditionDigest(request);
44984
+ if (request.precondition_digest !== expected) {
44985
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_DIGEST_MISMATCH", "precondition_digest does not match the exact transfer target and source snapshot", { expected_precondition_digest: expected });
44986
+ }
44987
+ const requestDigest = taskSubtreeTransferRequestDigest(request);
44988
+ const expectedKey = deriveTodosTaskSubtreeTransferIdempotencyKey({
44989
+ operation_id: request.operation_id,
44990
+ step_id: request.step_id,
44991
+ direction: "apply",
44992
+ target_selector: request.root_task_id,
44993
+ request_digest: requestDigest,
44994
+ precondition_digest: request.precondition_digest
44995
+ });
44996
+ if (request.idempotency_key !== expectedKey) {
44997
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_IDEMPOTENCY_MISMATCH", "idempotency_key does not match the exact transfer request", { expected_idempotency_key: expectedKey });
44998
+ }
44999
+ return this.backend.apply(request, this.backendOptions);
45000
+ }
45001
+ async readExact(receiptIdValue) {
45002
+ return this.backend.readExact(uuid3(receiptIdValue, "receipt_id"));
45003
+ }
45004
+ async rollback(input) {
45005
+ const request = parseTodosTaskSubtreeTransferRollback(input);
45006
+ const requestDigest = taskSubtreeTransferRollbackRequestDigest(request);
45007
+ const expectedKey = deriveTodosTaskSubtreeTransferIdempotencyKey({
45008
+ operation_id: request.operation_id,
45009
+ step_id: request.step_id,
45010
+ direction: "rollback",
45011
+ target_selector: request.receipt_id,
45012
+ request_digest: requestDigest,
45013
+ precondition_digest: request.precondition_digest
45014
+ });
45015
+ if (request.idempotency_key !== expectedKey) {
45016
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_IDEMPOTENCY_MISMATCH", "rollback idempotency_key does not match the exact rollback request", { expected_idempotency_key: expectedKey });
45017
+ }
45018
+ return this.backend.rollback(request, this.backendOptions);
45019
+ }
45020
+ }
45021
+ function createSqliteTodosTaskSubtreeTransferAuthority(options) {
45022
+ return new PackageOwnedTodosTaskSubtreeTransferAuthority(new SqliteTodosTaskSubtreeTransferBackend(options.database, resolveTenantId2(options.tenantId)), options);
45023
+ }
45024
+ function createPostgresTodosTaskSubtreeTransferAuthority(client, options = {}) {
45025
+ return new PackageOwnedTodosTaskSubtreeTransferAuthority(new PostgresTodosTaskSubtreeTransferBackend(client, options), options);
45026
+ }
45027
+ // src/task-subtree-transfer/http.ts
45028
+ var JSON_HEADERS3 = { "Content-Type": "application/json" };
45029
+ var REQUEST_BYTES = 2 * 1024 * 1024;
45030
+ var RESPONSE_BYTES = 8 * 1024 * 1024;
45031
+ function json3(body2, status2 = 200) {
45032
+ return new Response(JSON.stringify(body2), { status: status2, headers: JSON_HEADERS3 });
45033
+ }
45034
+ function status2(error) {
45035
+ switch (error.code) {
45036
+ case "TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT":
45037
+ case "TODOS_TASK_SUBTREE_TRANSFER_FOREIGN_REFERENCE":
45038
+ case "TODOS_TASK_SUBTREE_TRANSFER_HIERARCHY_CYCLE":
45039
+ return 400;
45040
+ case "TODOS_TASK_SUBTREE_TRANSFER_RECEIPT_NOT_FOUND":
45041
+ case "TODOS_TASK_SUBTREE_TRANSFER_NOT_FOUND":
45042
+ return 404;
45043
+ case "TODOS_TASK_SUBTREE_TRANSFER_ATOMICITY_UNAVAILABLE":
45044
+ return 503;
45045
+ default:
45046
+ return 409;
45047
+ }
45048
+ }
45049
+ async function boundedText2(message, limit) {
45050
+ if (!message.body)
45051
+ return "";
45052
+ const reader = message.body.getReader();
45053
+ const decoder = new TextDecoder;
45054
+ let bytes = 0;
45055
+ let text = "";
45056
+ try {
45057
+ while (true) {
45058
+ const { done, value } = await reader.read();
45059
+ if (done)
45060
+ break;
45061
+ bytes += value.byteLength;
45062
+ if (bytes > limit) {
45063
+ await reader.cancel();
45064
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "Task-subtree-transfer HTTP body exceeds the byte bound");
45065
+ }
45066
+ text += decoder.decode(value, { stream: true });
45067
+ }
45068
+ return text + decoder.decode();
45069
+ } finally {
45070
+ reader.releaseLock();
45071
+ }
45072
+ }
45073
+ async function readBody(request) {
45074
+ const declaredLength = Number(request.headers.get("content-length") ?? 0);
45075
+ if (Number.isFinite(declaredLength) && declaredLength > REQUEST_BYTES) {
45076
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "Task-subtree-transfer request exceeds the byte bound");
45077
+ }
45078
+ const text = await boundedText2(request, REQUEST_BYTES);
45079
+ try {
45080
+ return JSON.parse(text);
45081
+ } catch {
45082
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "Invalid JSON body");
45083
+ }
45084
+ }
45085
+ async function handleTodosTaskSubtreeTransferHttpRequest(request, url, authority, basePath = "/v1/task-subtree-transfer") {
45086
+ if (url.pathname !== basePath && !url.pathname.startsWith(`${basePath}/`))
45087
+ return null;
45088
+ const action = url.pathname.slice(basePath.length).split("/").filter(Boolean).join("/");
45089
+ try {
45090
+ if ((action === "" || action === "capability") && request.method === "GET") {
45091
+ return json3({ capability: await authority.capability() });
45092
+ }
45093
+ if (request.method !== "POST")
45094
+ return json3({ error: "method not allowed" }, 405);
45095
+ if (action === "inspect")
45096
+ return json3({ inspection: await authority.inspect(await readBody(request)) });
45097
+ if (action === "apply")
45098
+ return json3({ result: await authority.apply(await readBody(request)) }, 201);
45099
+ if (action === "rollback")
45100
+ return json3({ result: await authority.rollback(await readBody(request)) }, 201);
45101
+ if (action === "read-exact") {
45102
+ const input = await readBody(request);
45103
+ if (!input || typeof input.receipt_id !== "string") {
45104
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_INVALID_INPUT", "receipt_id is required");
45105
+ }
45106
+ return json3({ result: await authority.readExact(input.receipt_id) });
45107
+ }
45108
+ return json3({ error: "unknown task-subtree-transfer route" }, 404);
45109
+ } catch (cause) {
45110
+ if (cause instanceof TodosTaskSubtreeTransferError) {
45111
+ return json3({
45112
+ error: cause.message,
45113
+ code: cause.code,
45114
+ details: cause.details,
45115
+ authoritative: true
45116
+ }, status2(cause));
45117
+ }
45118
+ return json3({ error: "internal task-subtree-transfer error" }, 500);
45119
+ }
45120
+ }
45121
+
45122
+ class TodosTaskSubtreeTransferHttpClient {
45123
+ baseUrl;
45124
+ fetchImpl;
45125
+ headers;
45126
+ constructor(options) {
45127
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
45128
+ this.fetchImpl = options.fetch ?? globalThis.fetch;
45129
+ this.headers = {
45130
+ ...options.headers,
45131
+ ...options.apiKey ? { Authorization: `Bearer ${options.apiKey}` } : {},
45132
+ "Content-Type": "application/json"
45133
+ };
45134
+ }
45135
+ async request(action, init = {}) {
45136
+ const response = await this.fetchImpl(`${this.baseUrl}/v1/task-subtree-transfer${action}`, {
45137
+ ...init,
45138
+ headers: { ...this.headers, ...init.headers ?? {} }
45139
+ });
45140
+ const text = await boundedText2(response, RESPONSE_BYTES);
45141
+ let payload;
45142
+ try {
45143
+ payload = JSON.parse(text);
45144
+ } catch {
45145
+ throw new TodosTaskSubtreeTransferError("TODOS_TASK_SUBTREE_TRANSFER_HTTP_ERROR", `Task-subtree-transfer HTTP ${response.status} returned invalid JSON`);
45146
+ }
45147
+ if (!response.ok) {
45148
+ throw new TodosTaskSubtreeTransferError(typeof payload.code === "string" ? payload.code : "TODOS_TASK_SUBTREE_TRANSFER_HTTP_ERROR", typeof payload.error === "string" ? payload.error : `Task-subtree-transfer HTTP ${response.status}`, payload.details && typeof payload.details === "object" ? payload.details : {});
45149
+ }
45150
+ return payload;
45151
+ }
45152
+ async capability() {
45153
+ return (await this.request("/capability")).capability;
45154
+ }
45155
+ async inspect(input) {
45156
+ return (await this.request("/inspect", { method: "POST", body: JSON.stringify(input) })).inspection;
45157
+ }
45158
+ async apply(input) {
45159
+ return (await this.request("/apply", { method: "POST", body: JSON.stringify(input) })).result;
45160
+ }
45161
+ async readExact(receiptId3) {
45162
+ return (await this.request("/read-exact", { method: "POST", body: JSON.stringify({ receipt_id: receiptId3 }) })).result;
45163
+ }
45164
+ async rollback(input) {
45165
+ return (await this.request("/rollback", { method: "POST", body: JSON.stringify(input) })).result;
45166
+ }
45167
+ }
45168
+ function createTodosTaskSubtreeTransferHttpClient(options) {
45169
+ return new TodosTaskSubtreeTransferHttpClient(options);
45170
+ }
43627
45171
  // src/ai-tools.ts
43628
45172
  import { createHash as createHash16 } from "crypto";
43629
45173
  // src/cli/cloud-router.ts
@@ -43635,26 +45179,26 @@ var retryCapabilityCache = new Map;
43635
45179
  var taskCreatorCapabilityCache = new Map;
43636
45180
  var gitRefCapabilityCache = new Map;
43637
45181
  var remoteCommandCapabilityCache = new Map;
43638
- var LEGACY_STORAGE_MODE_KEYS = [
43639
- "HASNA_TODOS_STORAGE_MODE",
43640
- "HASNA_TODOS_MODE",
43641
- "TODOS_STORAGE_MODE",
43642
- "TODOS_MODE"
43643
- ];
43644
- function assertNoLegacyStorageMode(env = process.env) {
43645
- for (const key2 of LEGACY_STORAGE_MODE_KEYS) {
43646
- if (Object.hasOwn(env, key2) && env[key2] !== undefined) {
43647
- throw new Error(`REMOTE_STORAGE_MODE_REMOVED: ${key2} was removed. Deployment modes no longer exist: delete the storage-mode variable. ` + `The client uses the on-box SQLite store, or the HTTP API selected by ` + `HASNA_TODOS_API_URL + HASNA_TODOS_API_KEY. ` + `On the server, set HASNA_TODOS_DATABASE_URL to select the postgresql backend, ` + `or leave it unset for sqlite.`);
43648
- }
43649
- }
45182
+ var todosLocalFallbackNoticeEmitted = false;
45183
+ function emitTodosLocalFallbackNotice(env) {
45184
+ if (todosLocalFallbackNoticeEmitted)
45185
+ return;
45186
+ todosLocalFallbackNoticeEmitted = true;
45187
+ const notice = {
45188
+ event: "todos-local-fallback",
45189
+ transport: "sqlite",
45190
+ source: "default",
45191
+ apiUrlPresent: Boolean(env.HASNA_TODOS_API_URL?.trim()),
45192
+ apiKeyPresent: Boolean(env.HASNA_TODOS_API_KEY?.trim()),
45193
+ notice: "No hosted API config (HASNA_TODOS_API_URL + HASNA_TODOS_API_KEY) is present; " + "using local SQLite. Hosted todos are NOT visible in this output."
45194
+ };
45195
+ console.error(JSON.stringify(notice));
43650
45196
  }
43651
- function resolveTodosCliStorageMode(env = process.env) {
43652
- assertNoLegacyStorageMode(env);
45197
+ function resolveTodosCliTransport(env = process.env) {
43653
45198
  const urlValue = env.HASNA_TODOS_API_URL?.trim();
43654
45199
  const keyValue = env.HASNA_TODOS_API_KEY?.trim();
43655
45200
  if (urlValue && keyValue) {
43656
45201
  return {
43657
- mode: "http",
43658
45202
  transport: "http",
43659
45203
  selected: true,
43660
45204
  source: "HASNA_TODOS_API_URL+HASNA_TODOS_API_KEY"
@@ -43666,15 +45210,15 @@ function resolveTodosCliStorageMode(env = process.env) {
43666
45210
  if (keyValue) {
43667
45211
  throw new Error("REMOTE_API_URL_MISSING: remote Todos storage requires HASNA_TODOS_API_URL; local SQLite fallback is disabled");
43668
45212
  }
45213
+ emitTodosLocalFallbackNotice(env);
43669
45214
  return {
43670
- mode: "sqlite",
43671
45215
  transport: "sqlite",
43672
45216
  selected: false,
43673
45217
  source: "default"
43674
45218
  };
43675
45219
  }
43676
45220
  function requestedTransport(env) {
43677
- return resolveTodosCliStorageMode(env).transport;
45221
+ return resolveTodosCliTransport(env).transport;
43678
45222
  }
43679
45223
  function normalizeRemoteAuthorityUrl(value) {
43680
45224
  const trimmed = value?.trim();
@@ -43708,13 +45252,13 @@ function normalizeRemoteAuthorityUrl(value) {
43708
45252
  function getTodosRemoteAuthorityConfigStatus(env = process.env) {
43709
45253
  let resolution;
43710
45254
  try {
43711
- resolution = resolveTodosCliStorageMode(env);
45255
+ resolution = resolveTodosCliTransport(env);
43712
45256
  } catch (error) {
43713
45257
  const issue = error instanceof Error ? error.message : String(error);
43714
45258
  return {
43715
45259
  selected: true,
43716
45260
  ok: false,
43717
- mode: "invalid",
45261
+ transport: "invalid",
43718
45262
  api_url_configured: Boolean(env.HASNA_TODOS_API_URL?.trim()),
43719
45263
  api_key_configured: Boolean(env.HASNA_TODOS_API_KEY?.trim()),
43720
45264
  v1_base_url: null,
@@ -43722,12 +45266,12 @@ function getTodosRemoteAuthorityConfigStatus(env = process.env) {
43722
45266
  local_fallback: false
43723
45267
  };
43724
45268
  }
43725
- const { mode, selected } = resolution;
45269
+ const { transport, selected } = resolution;
43726
45270
  if (!selected) {
43727
45271
  return {
43728
45272
  selected: false,
43729
45273
  ok: true,
43730
- mode,
45274
+ transport,
43731
45275
  api_url_configured: false,
43732
45276
  api_key_configured: false,
43733
45277
  v1_base_url: null,
@@ -43752,7 +45296,7 @@ function getTodosRemoteAuthorityConfigStatus(env = process.env) {
43752
45296
  return {
43753
45297
  selected: true,
43754
45298
  ok: issues.length === 0,
43755
- mode,
45299
+ transport,
43756
45300
  api_url_configured: apiUrl !== null,
43757
45301
  api_key_configured: apiKeyConfigured,
43758
45302
  v1_base_url: apiUrl ? `${apiUrl}/v1` : null,
@@ -43761,24 +45305,24 @@ function getTodosRemoteAuthorityConfigStatus(env = process.env) {
43761
45305
  };
43762
45306
  }
43763
45307
  function requireTodosRemoteAuthorityEnv(env) {
43764
- const status2 = getTodosRemoteAuthorityConfigStatus(env);
43765
- if (!status2.ok)
43766
- throw new Error(status2.issues[0]);
45308
+ const status3 = getTodosRemoteAuthorityConfigStatus(env);
45309
+ if (!status3.ok)
45310
+ throw new Error(status3.issues[0]);
43767
45311
  return {
43768
45312
  ...env,
43769
- HASNA_TODOS_API_URL: status2.v1_base_url.replace(/\/v1$/, ""),
45313
+ HASNA_TODOS_API_URL: status3.v1_base_url.replace(/\/v1$/, ""),
43770
45314
  HASNA_TODOS_API_KEY: env.HASNA_TODOS_API_KEY.trim()
43771
45315
  };
43772
45316
  }
43773
45317
  function classifyRemoteRequestError(baseUrl, route, error) {
43774
- const status2 = error && typeof error === "object" ? error.status : undefined;
43775
- if (status2 === 401) {
45318
+ const status3 = error && typeof error === "object" ? error.status : undefined;
45319
+ if (status3 === 401) {
43776
45320
  throw new Error(`REMOTE_API_UNAUTHORIZED: configured Todos authority ${baseUrl} rejected HASNA_TODOS_API_KEY for ${route}; ` + "local SQLite fallback is disabled", { cause: error });
43777
45321
  }
43778
- if (status2 === 403) {
45322
+ if (status3 === 403) {
43779
45323
  throw new Error(`REMOTE_API_FORBIDDEN: configured Todos authority ${baseUrl} denied ${route}; local SQLite fallback is disabled`, { cause: error });
43780
45324
  }
43781
- if (status2 === 409) {
45325
+ if (status3 === 409) {
43782
45326
  const body2 = error && typeof error === "object" ? error.body : undefined;
43783
45327
  if (body2 && typeof body2 === "object" && !Array.isArray(body2)) {
43784
45328
  const remoteError = body2;
@@ -43788,17 +45332,18 @@ function classifyRemoteRequestError(baseUrl, route, error) {
43788
45332
  }
43789
45333
  throw error;
43790
45334
  }
43791
- if (typeof status2 === "number" && status2 >= 300 && status2 < 400) {
45335
+ if (typeof status3 === "number" && status3 >= 300 && status3 < 400) {
43792
45336
  throw new Error(`REMOTE_API_REDIRECT_REJECTED: configured Todos authority ${baseUrl} redirected ${route}; ` + "authenticated redirects are disabled to prevent credential leakage", { cause: error });
43793
45337
  }
43794
- if (typeof status2 === "number" && status2 >= 500) {
43795
- throw new Error(`REMOTE_API_UNAVAILABLE: configured Todos authority ${baseUrl} returned HTTP ${status2} for ${route}; ` + "local SQLite fallback is disabled", { cause: error });
45338
+ if (typeof status3 === "number" && status3 >= 500) {
45339
+ throw new Error(`REMOTE_API_UNAVAILABLE: configured Todos authority ${baseUrl} returned HTTP ${status3} for ${route}; ` + "local SQLite fallback is disabled", { cause: error });
43796
45340
  }
43797
45341
  const message = error instanceof Error ? error.message : String(error);
43798
45342
  if (error instanceof Error && error.name === "AbortError" || /abort|timed?\s*out/i.test(message)) {
43799
- throw new Error(`REMOTE_API_TIMEOUT: configured Todos authority ${baseUrl} timed out for ${route}; local SQLite fallback is disabled`, { cause: error });
45343
+ const unboundedNote = route === "/tasks" ? "; this is likely an UNBOUNDED task read timing out (bounded task reads from this authority return in ~1 s) \u2014 retry with a bounded/count-specific shape such as todos list --limit; local SQLite fallback is disabled" : "; local SQLite fallback is disabled";
45344
+ throw new Error(`REMOTE_API_TIMEOUT: configured Todos authority ${baseUrl} timed out for ${route}${unboundedNote}`, { cause: error });
43800
45345
  }
43801
- if (status2 === undefined) {
45346
+ if (status3 === undefined) {
43802
45347
  throw new Error(`REMOTE_API_UNREACHABLE: configured Todos authority ${baseUrl} could not be reached for ${route}; ` + "local SQLite fallback is disabled", { cause: error });
43803
45348
  }
43804
45349
  throw error;
@@ -43866,8 +45411,8 @@ async function requiredRemoteRoute(client, route, request, recognized404Codes =
43866
45411
  try {
43867
45412
  return await request();
43868
45413
  } catch (error) {
43869
- const status2 = error && typeof error === "object" ? error.status : undefined;
43870
- if (status2 === 404 || status2 === 405) {
45414
+ const status3 = error && typeof error === "object" ? error.status : undefined;
45415
+ if (status3 === 404 || status3 === 405) {
43871
45416
  const body2 = error && typeof error === "object" ? error.body : undefined;
43872
45417
  const code = body2 && typeof body2 === "object" && !Array.isArray(body2) ? body2.code : undefined;
43873
45418
  if (typeof code === "string" && recognized404Codes.includes(code))
@@ -43884,11 +45429,10 @@ function getTodosCloudClient(env = process.env, requestTimeoutMs = REMOTE_REQUES
43884
45429
  fetchImpl: (input, init) => globalThis.fetch(input, { ...init, redirect: "manual" }),
43885
45430
  timeoutMs: requestTimeoutMs
43886
45431
  });
43887
- if (resolved.transport === "cloud-http")
43888
- return protectRemoteClient(resolved.client, requestTimeoutMs);
43889
45432
  const transportName = resolved.transport;
43890
- if (transportName === "http")
45433
+ if (transportName === "cloud-http" || transportName === "http") {
43891
45434
  return protectRemoteClient(resolved.client, requestTimeoutMs);
45435
+ }
43892
45436
  return null;
43893
45437
  }
43894
45438
  function unwrapTask(raw) {
@@ -44039,9 +45583,9 @@ async function cloudListTasks(client, filter = {}) {
44039
45583
  const { status: _status, limit, offset, ...baseFilter } = filter;
44040
45584
  const start = offset ?? 0;
44041
45585
  const windowEnd = typeof limit === "number" ? start + limit : undefined;
44042
- const pages = await Promise.all(statuses.map((status2) => requestCloudTaskPage(client, {
45586
+ const pages = await Promise.all(statuses.map((status3) => requestCloudTaskPage(client, {
44043
45587
  ...baseFilter,
44044
- status: status2,
45588
+ status: status3,
44045
45589
  ...windowEnd === undefined ? {} : { limit: windowEnd }
44046
45590
  })));
44047
45591
  const seen = new Set;
@@ -44319,11 +45863,11 @@ init_audit();
44319
45863
  init_database();
44320
45864
  function upsertCheckpoint(task_id, step, updates, db) {
44321
45865
  const d = db || getDatabase();
44322
- const timestamp4 = now();
45866
+ const timestamp5 = now();
44323
45867
  const existing = d.query("SELECT id FROM task_checkpoints WHERE task_id = ? AND step = ?").get(task_id, step);
44324
45868
  const id = existing?.id ?? uuid();
44325
45869
  const agentId = updates.agent_id ?? null;
44326
- const status2 = updates.status ?? "pending";
45870
+ const status3 = updates.status ?? "pending";
44327
45871
  const data = updates.data ? JSON.stringify(updates.data) : JSON.stringify({});
44328
45872
  const error = updates.error ?? null;
44329
45873
  const attempt = updates.attempt ?? 1;
@@ -44337,22 +45881,22 @@ function upsertCheckpoint(task_id, step, updates, db) {
44337
45881
  task_id,
44338
45882
  agentId,
44339
45883
  step,
44340
- status2,
45884
+ status3,
44341
45885
  data,
44342
45886
  error,
44343
45887
  attempt,
44344
45888
  maxAttempts,
44345
45889
  startedAt,
44346
45890
  completedAt2,
44347
- timestamp4,
44348
- status2,
45891
+ timestamp5,
45892
+ status3,
44349
45893
  data,
44350
45894
  error,
44351
45895
  attempt,
44352
45896
  maxAttempts,
44353
45897
  startedAt,
44354
45898
  completedAt2,
44355
- timestamp4
45899
+ timestamp5
44356
45900
  ]);
44357
45901
  return rowToCheckpoint(d.query("SELECT * FROM task_checkpoints WHERE id = ?").get(id));
44358
45902
  }
@@ -44415,10 +45959,10 @@ function stepForGate(gate) {
44415
45959
  throw new Error("Approval gate name is required");
44416
45960
  return `approval:${trimmed}`;
44417
45961
  }
44418
- function approvalStatusToCheckpointStatus(status2) {
44419
- if (status2 === "approved")
45962
+ function approvalStatusToCheckpointStatus(status3) {
45963
+ if (status3 === "approved")
44420
45964
  return "completed";
44421
- if (status2 === "pending")
45965
+ if (status3 === "pending")
44422
45966
  return "pending";
44423
45967
  return "failed";
44424
45968
  }
@@ -44440,12 +45984,12 @@ function isExpired(expiresAt, at = new Date) {
44440
45984
  return Boolean(expiresAt && new Date(expiresAt).getTime() <= at.getTime());
44441
45985
  }
44442
45986
  function gateFromCheckpoint(checkpoint) {
44443
- const status2 = checkpointStatusToApprovalStatus(checkpoint);
45987
+ const status3 = checkpointStatusToApprovalStatus(checkpoint);
44444
45988
  return {
44445
45989
  id: checkpoint.id,
44446
45990
  task_id: checkpoint.task_id,
44447
45991
  gate: String(checkpoint.data["approval_gate_name"] || checkpoint.step.replace(/^approval:/, "")),
44448
- status: status2,
45992
+ status: status3,
44449
45993
  reviewer: typeof checkpoint.data["reviewer"] === "string" ? checkpoint.data["reviewer"] : null,
44450
45994
  requester: typeof checkpoint.data["requester"] === "string" ? checkpoint.data["requester"] : null,
44451
45995
  reason: typeof checkpoint.data["reason"] === "string" ? checkpoint.data["reason"] : null,
@@ -44490,13 +46034,13 @@ function logApprovalEvent(taskId, action, gate, agentId, db) {
44490
46034
  });
44491
46035
  }
44492
46036
  }
44493
- function writeGate(input, status2, decision, db) {
46037
+ function writeGate(input, status3, decision, db) {
44494
46038
  const d = db || getDatabase();
44495
46039
  ensureTask(input.task_id, d);
44496
46040
  const step = stepForGate(input.gate);
44497
46041
  const existing = getCheckpoint(input.task_id, step, d);
44498
46042
  const existingData = existing?.data || {};
44499
- const timestamp4 = decision?.decided_at || now();
46043
+ const timestamp5 = decision?.decided_at || now();
44500
46044
  const existingRunId = typeof existingData["run_id"] === "string" ? existingData["run_id"] : undefined;
44501
46045
  const runId = input.run_id ? resolveTaskRunId(input.run_id, d) : existingRunId;
44502
46046
  const data = {
@@ -44504,7 +46048,7 @@ function writeGate(input, status2, decision, db) {
44504
46048
  ...input.metadata || {},
44505
46049
  approval_gate: true,
44506
46050
  approval_gate_name: input.gate.trim(),
44507
- approval_status: status2,
46051
+ approval_status: status3,
44508
46052
  requester: input.requester ?? existingData["requester"] ?? null,
44509
46053
  reviewer: input.reviewer ?? decision?.decided_by ?? existingData["reviewer"] ?? null,
44510
46054
  reason: decision?.reason ?? input.reason ?? existingData["reason"] ?? null,
@@ -44517,11 +46061,11 @@ function writeGate(input, status2, decision, db) {
44517
46061
  };
44518
46062
  const checkpoint = upsertCheckpoint(input.task_id, step, {
44519
46063
  agent_id: decision?.decided_by || input.requester || input.reviewer,
44520
- status: approvalStatusToCheckpointStatus(status2),
46064
+ status: approvalStatusToCheckpointStatus(status3),
44521
46065
  data,
44522
- error: status2 === "rejected" || status2 === "expired" ? String(data.reason || status2) : null,
44523
- started_at: existing?.started_at || timestamp4,
44524
- completed_at: status2 === "pending" ? null : timestamp4
46066
+ error: status3 === "rejected" || status3 === "expired" ? String(data.reason || status3) : null,
46067
+ started_at: existing?.started_at || timestamp5,
46068
+ completed_at: status3 === "pending" ? null : timestamp5
44525
46069
  }, d);
44526
46070
  return gateFromCheckpoint(checkpoint);
44527
46071
  }
@@ -45369,7 +46913,7 @@ async function executeListTasks(adapter, state, request, input) {
45369
46913
  const planId = boundedOptionalString(record, "plan_id", TODOS_AI_READ_TOOL_LIMITS.max_identifier_bytes);
45370
46914
  const taskListId = boundedOptionalString(record, "task_list_id", TODOS_AI_READ_TOOL_LIMITS.max_identifier_bytes);
45371
46915
  const assignedTo = boundedOptionalString(record, "assigned_to", TODOS_AI_READ_TOOL_LIMITS.max_filter_string_bytes);
45372
- const status2 = boundedEnum(record, "status", TASK_STATUSES);
46916
+ const status3 = boundedEnum(record, "status", TASK_STATUSES);
45373
46917
  const priority = boundedEnum(record, "priority", TASK_PRIORITIES);
45374
46918
  const tags = boundedTags(record);
45375
46919
  if (projectId !== undefined)
@@ -45380,8 +46924,8 @@ async function executeListTasks(adapter, state, request, input) {
45380
46924
  filter.task_list_id = taskListId;
45381
46925
  if (assignedTo !== undefined)
45382
46926
  filter.assigned_to = assignedTo;
45383
- if (status2 !== undefined)
45384
- filter.status = status2;
46927
+ if (status3 !== undefined)
46928
+ filter.status = status3;
45385
46929
  if (priority !== undefined)
45386
46930
  filter.priority = priority;
45387
46931
  if (tags !== undefined)
@@ -45723,29 +47267,29 @@ function getNativeStorageStatus(env = process.env) {
45723
47267
  };
45724
47268
  }
45725
47269
  function getNativeStorageSyncPlan(env = process.env, options = {}) {
45726
- const status2 = getNativeStorageStatus(env);
47270
+ const status3 = getNativeStorageStatus(env);
45727
47271
  const steps = [
45728
47272
  "Read local SQLite snapshot",
45729
- status2.remote_enabled ? "Prepare Postgres sync table upserts" : "Skip remote database writes in local mode",
45730
- status2.object_storage.configured ? "Plan S3 artifact object writes" : "Keep artifact storage local",
47273
+ status3.remote_enabled ? "Prepare Postgres sync table upserts" : "Skip remote database writes in local mode",
47274
+ status3.object_storage.configured ? "Plan S3 artifact object writes" : "Keep artifact storage local",
45731
47275
  "Report planned changes without opening network connections"
45732
47276
  ];
45733
47277
  return {
45734
- ok: status2.ok,
47278
+ ok: status3.ok,
45735
47279
  service: "todos",
45736
47280
  dry_run: true,
45737
47281
  no_network: true,
45738
- status: status2,
47282
+ status: status3,
45739
47283
  postgres: {
45740
- required: status2.remote_enabled,
45741
- configured: status2.database.configured,
47284
+ required: status3.remote_enabled,
47285
+ configured: status3.database.configured,
45742
47286
  schema_sql: options.includeSchemaSql ? postgresTodosSyncSchemaSql() : []
45743
47287
  },
45744
47288
  object_storage: {
45745
- required: status2.remote_enabled,
45746
- configured: status2.object_storage.configured,
45747
- bucket: status2.object_storage.bucket,
45748
- prefix: status2.object_storage.prefix
47289
+ required: status3.remote_enabled,
47290
+ configured: status3.object_storage.configured,
47291
+ bucket: status3.object_storage.bucket,
47292
+ prefix: status3.object_storage.prefix
45749
47293
  },
45750
47294
  steps
45751
47295
  };
@@ -45851,8 +47395,8 @@ function resolveTaskList(task3, project, db) {
45851
47395
  }
45852
47396
  return null;
45853
47397
  }
45854
- function isTerminal2(status2) {
45855
- return status2 === "completed" || status2 === "cancelled" || status2 === "failed";
47398
+ function isTerminal2(status3) {
47399
+ return status3 === "completed" || status3 === "cancelled" || status3 === "failed";
45856
47400
  }
45857
47401
  function routeConcurrencyKey(task3, project, taskList, projectPath) {
45858
47402
  if (project?.id)
@@ -46033,7 +47577,7 @@ function setTaskWorkflowPointers(taskId, input, db) {
46033
47577
  latest_evaluation_path: pointerPatch(previous.latest_evaluation_path, input, "latest_evaluation_path"),
46034
47578
  workflow_state: pointerPatch(previous.workflow_state, input, "workflow_state")
46035
47579
  });
46036
- const timestamp4 = now();
47580
+ const timestamp5 = now();
46037
47581
  const {
46038
47582
  current_workflow_invocation_id,
46039
47583
  current_run_id,
@@ -46049,7 +47593,7 @@ function setTaskWorkflowPointers(taskId, input, db) {
46049
47593
  workflow_invocation: {
46050
47594
  schema_version: TASK_WORKFLOW_POINTER_SCHEMA_VERSION,
46051
47595
  ...next,
46052
- updated_at: timestamp4,
47596
+ updated_at: timestamp5,
46053
47597
  updated_by: input.actor ?? null
46054
47598
  }
46055
47599
  };
@@ -46083,8 +47627,8 @@ function normalizePath3(input) {
46083
47627
  return resolve10(input);
46084
47628
  }
46085
47629
  function sourceStoreId(sourceDbPath) {
46086
- const digest2 = createHash17("sha256").update(sourceDbPath).digest("hex").slice(0, 16);
46087
- return `sqlite:${digest2}`;
47630
+ const digest3 = createHash17("sha256").update(sourceDbPath).digest("hex").slice(0, 16);
47631
+ return `sqlite:${digest3}`;
46088
47632
  }
46089
47633
  function inferSourceRepoPath(sourceDbPath) {
46090
47634
  const normalized = normalizePath3(sourceDbPath);
@@ -46855,7 +48399,7 @@ function rowToSnapshot(row) {
46855
48399
  function saveSnapshot(input, db) {
46856
48400
  const d = db || getDatabase();
46857
48401
  const id = uuid();
46858
- const timestamp4 = now();
48402
+ const timestamp5 = now();
46859
48403
  d.run(`INSERT INTO context_snapshots (id, agent_id, task_id, project_id, snapshot_type, plan_summary, files_open, attempts, blockers, next_steps, metadata, created_at)
46860
48404
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
46861
48405
  id,
@@ -46869,7 +48413,7 @@ function saveSnapshot(input, db) {
46869
48413
  JSON.stringify(input.blockers || []),
46870
48414
  input.next_steps || null,
46871
48415
  JSON.stringify(input.metadata || {}),
46872
- timestamp4
48416
+ timestamp5
46873
48417
  ]);
46874
48418
  return {
46875
48419
  id,
@@ -46883,7 +48427,7 @@ function saveSnapshot(input, db) {
46883
48427
  blockers: input.blockers || [],
46884
48428
  next_steps: input.next_steps || null,
46885
48429
  metadata: input.metadata || {},
46886
- created_at: timestamp4
48430
+ created_at: timestamp5
46887
48431
  };
46888
48432
  }
46889
48433
  function getLatestSnapshot(agentId, taskId, db) {
@@ -46999,7 +48543,7 @@ function createKnowledgeRecord(input, db) {
46999
48543
  if (!title)
47000
48544
  throw new Error("Knowledge record title is required");
47001
48545
  const d = db || getDatabase();
47002
- const timestamp4 = now();
48546
+ const timestamp5 = now();
47003
48547
  const id = uuid();
47004
48548
  d.run(`INSERT INTO project_knowledge_records (
47005
48549
  id, record_type, title, content, decision, rationale, alternatives,
@@ -47019,8 +48563,8 @@ function createKnowledgeRecord(input, db) {
47019
48563
  input.snapshot_id || null,
47020
48564
  JSON.stringify(normalizeTags(input.tags)),
47021
48565
  JSON.stringify(input.metadata || {}),
47022
- timestamp4,
47023
- timestamp4
48566
+ timestamp5,
48567
+ timestamp5
47024
48568
  ]);
47025
48569
  return getKnowledgeRecord(id, d);
47026
48570
  }
@@ -47211,9 +48755,9 @@ function normalizeText4(value) {
47211
48755
  const trimmed = value.trim();
47212
48756
  return trimmed.length > 0 ? trimmed : null;
47213
48757
  }
47214
- function assertStatus(status2) {
47215
- if (!VALID_STATUSES.has(status2))
47216
- throw new Error(`Invalid risk status: ${status2}`);
48758
+ function assertStatus(status3) {
48759
+ if (!VALID_STATUSES.has(status3))
48760
+ throw new Error(`Invalid risk status: ${status3}`);
47217
48761
  }
47218
48762
  function assertSeverity(severity) {
47219
48763
  if (!VALID_SEVERITIES2.has(severity))
@@ -47258,16 +48802,16 @@ function createRisk(input, db) {
47258
48802
  const title = input.title.trim();
47259
48803
  if (!title)
47260
48804
  throw new Error("Risk title is required");
47261
- const status2 = input.status || "open";
48805
+ const status3 = input.status || "open";
47262
48806
  const severity = input.severity || "medium";
47263
48807
  const probability = input.probability || "medium";
47264
- assertStatus(status2);
48808
+ assertStatus(status3);
47265
48809
  assertSeverity(severity);
47266
48810
  assertProbability(probability);
47267
48811
  const d = db || getDatabase();
47268
- const timestamp4 = now();
48812
+ const timestamp5 = now();
47269
48813
  const id = uuid();
47270
- const closedAt = status2 === "resolved" || status2 === "accepted" ? timestamp4 : null;
48814
+ const closedAt = status3 === "resolved" || status3 === "accepted" ? timestamp5 : null;
47271
48815
  d.run(`INSERT INTO project_risks (
47272
48816
  id, title, description, status, severity, probability, owner, mitigation, due_at,
47273
48817
  project_id, plan_id, task_id, tags, metadata, created_at, updated_at, closed_at
@@ -47275,7 +48819,7 @@ function createRisk(input, db) {
47275
48819
  id,
47276
48820
  title,
47277
48821
  normalizeText4(input.description),
47278
- status2,
48822
+ status3,
47279
48823
  severity,
47280
48824
  probability,
47281
48825
  normalizeText4(input.owner),
@@ -47286,8 +48830,8 @@ function createRisk(input, db) {
47286
48830
  resolveKnownId2("tasks", input.task_id, d),
47287
48831
  JSON.stringify(normalizeTags2(input.tags)),
47288
48832
  JSON.stringify(input.metadata || {}),
47289
- timestamp4,
47290
- timestamp4,
48833
+ timestamp5,
48834
+ timestamp5,
47291
48835
  closedAt
47292
48836
  ]);
47293
48837
  return getRisk(id, d);
@@ -47370,8 +48914,8 @@ function updateRisk(id, input, db) {
47370
48914
  d.run(`UPDATE project_risks SET ${sets.join(", ")} WHERE id = ?`, params);
47371
48915
  return getRisk(resolved, d);
47372
48916
  }
47373
- function closeRisk(id, status2 = "resolved", db) {
47374
- return updateRisk(id, { status: status2 }, db);
48917
+ function closeRisk(id, status3 = "resolved", db) {
48918
+ return updateRisk(id, { status: status3 }, db);
47375
48919
  }
47376
48920
  function buildRiskFilters(options, db) {
47377
48921
  const conditions = [];
@@ -47469,7 +49013,7 @@ function scoreHealth(scope, scopeId, db) {
47469
49013
  const blockers = d.query(`SELECT dep.id, dep.short_id, dep.title, dep.status
47470
49014
  FROM task_dependencies td
47471
49015
  JOIN tasks dep ON dep.id = td.depends_on
47472
- 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);
47473
49017
  return { id: task3.id, short_id: task3.short_id, title: redactEvidenceText(task3.title), blockers };
47474
49018
  }).filter((entry2) => entry2.blockers.length > 0);
47475
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 }));
@@ -47499,7 +49043,7 @@ function scoreHealth(scope, scopeId, db) {
47499
49043
  };
47500
49044
  const totalPenalty = Object.values(penalties).reduce((sum, penalty) => sum + penalty, 0);
47501
49045
  const score = Math.max(0, 100 - Math.min(100, totalPenalty));
47502
- const status2 = score < 40 ? "critical" : score < 70 ? "at_risk" : score < 85 ? "watch" : "healthy";
49046
+ const status3 = score < 40 ? "critical" : score < 70 ? "at_risk" : score < 85 ? "watch" : "healthy";
47503
49047
  const recommendations = [];
47504
49048
  if (blocked.length > 0)
47505
49049
  recommendations.push("Clear blocking dependencies before starting more work.");
@@ -47523,7 +49067,7 @@ function scoreHealth(scope, scopeId, db) {
47523
49067
  scope,
47524
49068
  scope_id: scopeInfo.id,
47525
49069
  score,
47526
- status: status2,
49070
+ status: status3,
47527
49071
  components: {
47528
49072
  total_tasks: tasks.length,
47529
49073
  blocked_tasks: blocked.length,
@@ -47770,7 +49314,7 @@ function createRetrospective(input, db) {
47770
49314
  const d = db || getDatabase();
47771
49315
  const report = buildReport(input, d);
47772
49316
  const scope = scopeFromInput(input, d);
47773
- const timestamp4 = now();
49317
+ const timestamp5 = now();
47774
49318
  if (input.create_followups) {
47775
49319
  for (const followUp of report.follow_up_tasks) {
47776
49320
  const task3 = createTask({
@@ -47786,7 +49330,7 @@ function createRetrospective(input, db) {
47786
49330
  }
47787
49331
  const id = uuid();
47788
49332
  d.run(`INSERT INTO local_retrospectives (id, title, scope, project_id, plan_id, agent_id, report_json, created_at, updated_at)
47789
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, report.title, report.scope, scope.projectId, scope.planId, input.agent_id || null, JSON.stringify(report), timestamp4, timestamp4]);
49333
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, report.title, report.scope, scope.projectId, scope.planId, input.agent_id || null, JSON.stringify(report), timestamp5, timestamp5]);
47790
49334
  return getRetrospective(id, d);
47791
49335
  }
47792
49336
  function getRetrospective(id, db) {
@@ -48075,7 +49619,7 @@ function createApiKey(input, db) {
48075
49619
  if (!name)
48076
49620
  throw new Error("API key name is required");
48077
49621
  const key2 = generatePlaintextKey();
48078
- const timestamp4 = now();
49622
+ const timestamp5 = now();
48079
49623
  const id = uuid();
48080
49624
  const prefix = key2.slice(0, 12);
48081
49625
  d.run(`INSERT INTO api_keys (id, name, key_hash, prefix, permissions, created_at, expires_at)
@@ -48085,7 +49629,7 @@ function createApiKey(input, db) {
48085
49629
  hashApiKey(key2),
48086
49630
  prefix,
48087
49631
  JSON.stringify(input.permissions?.length ? input.permissions : ["*"]),
48088
- timestamp4,
49632
+ timestamp5,
48089
49633
  input.expires_at || null
48090
49634
  ]);
48091
49635
  const row = d.query("SELECT * FROM api_keys WHERE id = ?").get(id);
@@ -48139,15 +49683,15 @@ function rowToSession(row) {
48139
49683
  function createSession(input, db) {
48140
49684
  const d = db || getDatabase();
48141
49685
  const id = uuid();
48142
- const timestamp4 = now();
49686
+ const timestamp5 = now();
48143
49687
  d.run(`INSERT INTO sessions (id, agent_id, project_id, working_dir, started_at, last_activity, metadata)
48144
49688
  VALUES (?, ?, ?, ?, ?, ?, ?)`, [
48145
49689
  id,
48146
49690
  input.agent_id || null,
48147
49691
  input.project_id || null,
48148
49692
  input.working_dir || null,
48149
- timestamp4,
48150
- timestamp4,
49693
+ timestamp5,
49694
+ timestamp5,
48151
49695
  JSON.stringify(input.metadata || {})
48152
49696
  ]);
48153
49697
  return getSession(id, d);
@@ -48320,7 +49864,7 @@ function rowToArtifact2(row) {
48320
49864
  function addArtifact(input, db, dbPath) {
48321
49865
  const d = db || getDatabase();
48322
49866
  const id = uuid();
48323
- const timestamp4 = now();
49867
+ const timestamp5 = now();
48324
49868
  const storageMode = input.storage_mode ?? "copy";
48325
49869
  const stored = storeArtifactFile({
48326
49870
  artifactId: id,
@@ -48345,8 +49889,8 @@ function addArtifact(input, db, dbPath) {
48345
49889
  stored.sizeBytes,
48346
49890
  input.redaction_status ?? "none",
48347
49891
  JSON.stringify(input.metadata ?? {}),
48348
- timestamp4,
48349
- timestamp4
49892
+ timestamp5,
49893
+ timestamp5
48350
49894
  ]);
48351
49895
  return getArtifact(id, d);
48352
49896
  }
@@ -48379,14 +49923,14 @@ function listArtifacts(filter = {}, db) {
48379
49923
  }
48380
49924
  function updateArtifactRedaction(id, redactionStatus, db) {
48381
49925
  const d = db || getDatabase();
48382
- const timestamp4 = now();
48383
- d.run("UPDATE artifacts SET redaction_status = ?, updated_at = ? WHERE id = ? AND deleted_at IS NULL", [redactionStatus, timestamp4, id]);
49926
+ const timestamp5 = now();
49927
+ d.run("UPDATE artifacts SET redaction_status = ?, updated_at = ? WHERE id = ? AND deleted_at IS NULL", [redactionStatus, timestamp5, id]);
48384
49928
  return getArtifact(id, d);
48385
49929
  }
48386
49930
  function softDeleteArtifact(id, db) {
48387
49931
  const d = db || getDatabase();
48388
- const timestamp4 = now();
48389
- const result = d.run("UPDATE artifacts SET deleted_at = ?, updated_at = ? WHERE id = ? AND deleted_at IS NULL", [timestamp4, timestamp4, id]);
49932
+ const timestamp5 = now();
49933
+ const result = d.run("UPDATE artifacts SET deleted_at = ?, updated_at = ? WHERE id = ? AND deleted_at IS NULL", [timestamp5, timestamp5, id]);
48390
49934
  return result.changes > 0;
48391
49935
  }
48392
49936
  function purgeArtifact(id, db, dbPath) {
@@ -48524,7 +50068,7 @@ init_redaction();
48524
50068
  function createHandoff(input, db) {
48525
50069
  const d = db || getDatabase();
48526
50070
  const id = uuid();
48527
- const timestamp4 = now();
50071
+ const timestamp5 = now();
48528
50072
  d.run(`INSERT INTO handoffs (id, agent_id, project_id, session_id, summary, completed, in_progress, blockers, next_steps, task_ids, relevant_files, run_ids, created_at)
48529
50073
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
48530
50074
  id,
@@ -48539,7 +50083,7 @@ function createHandoff(input, db) {
48539
50083
  toJson(input.task_ids),
48540
50084
  toJson(input.relevant_files),
48541
50085
  toJson(input.run_ids),
48542
- timestamp4
50086
+ timestamp5
48543
50087
  ]);
48544
50088
  return {
48545
50089
  id,
@@ -48554,7 +50098,7 @@ function createHandoff(input, db) {
48554
50098
  task_ids: input.task_ids || null,
48555
50099
  relevant_files: input.relevant_files || null,
48556
50100
  run_ids: input.run_ids || null,
48557
- created_at: timestamp4,
50101
+ created_at: timestamp5,
48558
50102
  acknowledged_by: []
48559
50103
  };
48560
50104
  }
@@ -49066,7 +50610,7 @@ function upsertVerificationProvider(input) {
49066
50610
  const name = normalizeName(input.name);
49067
50611
  const config = loadConfig();
49068
50612
  const existing = config.verification_providers?.[name];
49069
- const timestamp4 = new Date().toISOString();
50613
+ const timestamp5 = new Date().toISOString();
49070
50614
  const provider = {
49071
50615
  ...existing,
49072
50616
  name,
@@ -49077,8 +50621,8 @@ function upsertVerificationProvider(input) {
49077
50621
  capabilities: input.capabilities ?? existing?.capabilities,
49078
50622
  retry: input.retry ? retryConfig(input.retry) : existing?.retry,
49079
50623
  timeout_ms: timeoutMs(input.timeout_ms ?? existing?.timeout_ms),
49080
- created_at: existing?.created_at || timestamp4,
49081
- updated_at: timestamp4
50624
+ created_at: existing?.created_at || timestamp5,
50625
+ updated_at: timestamp5
49082
50626
  };
49083
50627
  saveConfig({
49084
50628
  ...config,
@@ -49639,7 +51183,7 @@ function upsertPolicyPack(input) {
49639
51183
  const existing = config.policy_packs?.[input.name];
49640
51184
  const root = normalizePath4(input.root || existing?.root || process.cwd());
49641
51185
  const base = existing || defaultPolicyPack(input.name, root);
49642
- const timestamp4 = new Date().toISOString();
51186
+ const timestamp5 = new Date().toISOString();
49643
51187
  const pack = {
49644
51188
  ...base,
49645
51189
  name: input.name,
@@ -49657,8 +51201,8 @@ function upsertPolicyPack(input) {
49657
51201
  require_artifact: input.require_artifact ?? base.require_artifact,
49658
51202
  evidence_min_count: input.evidence_min_count ?? base.evidence_min_count,
49659
51203
  branch_pattern: input.branch_pattern ?? base.branch_pattern,
49660
- created_at: existing?.created_at || timestamp4,
49661
- updated_at: timestamp4
51204
+ created_at: existing?.created_at || timestamp5,
51205
+ updated_at: timestamp5
49662
51206
  };
49663
51207
  saveConfig({
49664
51208
  ...config,
@@ -50000,7 +51544,7 @@ function summarizeOutput(stdout, stderr) {
50000
51544
  function upsertAgentRunAdapter(input) {
50001
51545
  const config = loadConfig();
50002
51546
  const existing = config.agent_run_adapters?.[input.name];
50003
- const timestamp4 = new Date().toISOString();
51547
+ const timestamp5 = new Date().toISOString();
50004
51548
  const adapter = {
50005
51549
  ...existing,
50006
51550
  name: input.name,
@@ -50008,8 +51552,8 @@ function upsertAgentRunAdapter(input) {
50008
51552
  sandbox: input.sandbox ?? existing?.sandbox,
50009
51553
  cwd: input.cwd ?? existing?.cwd,
50010
51554
  env: input.env ?? existing?.env,
50011
- created_at: existing?.created_at || timestamp4,
50012
- updated_at: timestamp4
51555
+ created_at: existing?.created_at || timestamp5,
51556
+ updated_at: timestamp5
50013
51557
  };
50014
51558
  saveConfig({
50015
51559
  ...config,
@@ -50133,7 +51677,7 @@ async function runNextAgentDispatch(input = {}, db) {
50133
51677
  proc.exited
50134
51678
  ]);
50135
51679
  const outputSummary = summarizeOutput(stdout, stderr);
50136
- const status2 = exitCode === 0 ? "completed" : "failed";
51680
+ const status3 = exitCode === 0 ? "completed" : "failed";
50137
51681
  addTaskRunCommand({
50138
51682
  run_id: next.run.id,
50139
51683
  command,
@@ -50142,15 +51686,15 @@ async function runNextAgentDispatch(input = {}, db) {
50142
51686
  output_summary: outputSummary ?? undefined,
50143
51687
  agent_id: next.run.agent_id ?? undefined
50144
51688
  }, d);
50145
- const completed = { ...running, state: status2, completed_at: new Date().toISOString(), last_error: exitCode === 0 ? undefined : outputSummary || `exit ${exitCode}` };
51689
+ const completed = { ...running, state: status3, completed_at: new Date().toISOString(), last_error: exitCode === 0 ? undefined : outputSummary || `exit ${exitCode}` };
50146
51690
  updateDispatcherMetadata(next.run.id, completed, d);
50147
51691
  finishTaskRun({
50148
51692
  run_id: next.run.id,
50149
- status: status2,
50150
- summary: outputSummary || `agent run ${status2}`,
51693
+ status: status3,
51694
+ summary: outputSummary || `agent run ${status3}`,
50151
51695
  agent_id: next.run.agent_id ?? undefined
50152
51696
  }, d);
50153
- return { run_id: next.run.id, task_id: next.run.task_id, command, dry_run: false, status: status2, exit_code: exitCode, output_summary: outputSummary };
51697
+ return { run_id: next.run.id, task_id: next.run.task_id, command, dry_run: false, status: status3, exit_code: exitCode, output_summary: outputSummary };
50154
51698
  }
50155
51699
  function listAgentRuns(filter = {}, db) {
50156
51700
  let runs = listAgentRunQueue(db).map(toAgentRun);
@@ -51119,10 +52663,10 @@ function extractEmbeddedBridge(markdown) {
51119
52663
  const match = markdown.match(/<!--\s*hasna\.todos\.bridge\s*\n([\s\S]*?)\n\s*-->/);
51120
52664
  if (!match)
51121
52665
  return null;
51122
- const json3 = match[1].split(`
52666
+ const json4 = match[1].split(`
51123
52667
  `).map((line) => line.replace(/^ /, "")).join(`
51124
52668
  `);
51125
- return JSON.parse(json3);
52669
+ return JSON.parse(json4);
51126
52670
  }
51127
52671
  function frontmatterValue(markdown, key2) {
51128
52672
  const match = markdown.match(/^---\n([\s\S]*?)\n---/);
@@ -51143,14 +52687,14 @@ function parseTaskLine(line, currentPlan) {
51143
52687
  const match = line.match(/^\s*[-*]\s+\[([ xX])\]\s+(.+)$/);
51144
52688
  if (!match)
51145
52689
  return null;
51146
- const status2 = match[1].toLowerCase() === "x" ? "completed" : "pending";
52690
+ const status3 = match[1].toLowerCase() === "x" ? "completed" : "pending";
51147
52691
  const rawTitle = stripInlineMetadata(match[2]).replace(/^\*\*(.*?)\*\*$/, "$1");
51148
52692
  const tags = [...rawTitle.matchAll(/(^|\s)#([A-Za-z0-9_-]+)/g)].map((tag) => tag[2]);
51149
52693
  const assignee = rawTitle.match(/(^|\s)@([A-Za-z0-9_-]+)/)?.[2];
51150
52694
  const title = rawTitle.replace(/(^|\s)#[A-Za-z0-9_-]+/g, "").replace(/(^|\s)@[A-Za-z0-9_-]+/g, "").replace(/\s+/g, " ").trim();
51151
52695
  return {
51152
52696
  title,
51153
- status: status2,
52697
+ status: status3,
51154
52698
  priority: "medium",
51155
52699
  tags,
51156
52700
  assigned_to: assignee,
@@ -52755,7 +54299,7 @@ function normalizeJiraIssue(raw) {
52755
54299
  if (!summary)
52756
54300
  return null;
52757
54301
  const statusRec = asRecord3(fields.status);
52758
- const status2 = typeof statusRec?.name === "string" ? statusRec.name : undefined;
54302
+ const status3 = typeof statusRec?.name === "string" ? statusRec.name : undefined;
52759
54303
  const labels = labelNames2(fields.labels);
52760
54304
  const description = typeof fields.description === "string" ? fields.description.slice(0, 8000) : typeof asRecord3(fields.description)?.content === "string" ? String(asRecord3(fields.description)?.content).slice(0, 8000) : undefined;
52761
54305
  return {
@@ -52764,13 +54308,13 @@ function normalizeJiraIssue(raw) {
52764
54308
  external_id: key2,
52765
54309
  title: `[${key2}] ${summary}`,
52766
54310
  description,
52767
- status: status2,
54311
+ status: status3,
52768
54312
  priority: priorityFromJira(fields.priority),
52769
54313
  tags: [...labels.slice(0, 10), "jira-import"],
52770
54314
  url: typeof raw.self === "string" ? raw.self : undefined,
52771
54315
  raw_metadata: {
52772
54316
  jira_key: key2,
52773
- jira_status: status2,
54317
+ jira_status: status3,
52774
54318
  jira_priority: asRecord3(fields.priority)?.name,
52775
54319
  jira_id: raw.id
52776
54320
  }
@@ -55515,6 +57059,10 @@ function importBundle(bundle, options = {}, db) {
55515
57059
  }
55516
57060
  for (const dep of bundle.dependencies) {
55517
57061
  try {
57062
+ if (!getTask(dep.task_id, d) || !getTask(dep.depends_on, d)) {
57063
+ bump(result.skipped, "dependencies");
57064
+ continue;
57065
+ }
55518
57066
  d.run("INSERT OR IGNORE INTO task_dependencies (task_id, depends_on) VALUES (?, ?)", [dep.task_id, dep.depends_on]);
55519
57067
  bump(result.created, "dependencies");
55520
57068
  } catch (e) {
@@ -56320,11 +57868,11 @@ function createTuiDashboardSnapshot(options = {}, db) {
56320
57868
  const d = db || getDatabase();
56321
57869
  const limit = limited(options.limit);
56322
57870
  const filter = projectFilter(options.project_id);
56323
- const counts = TASK_STATUSES2.reduce((acc, status2) => {
56324
- acc[status2] = countTasks({ ...filter, status: status2 }, d);
57871
+ const counts = TASK_STATUSES2.reduce((acc, status3) => {
57872
+ acc[status3] = countTasks({ ...filter, status: status3 }, d);
56325
57873
  return acc;
56326
57874
  }, { total: 0 });
56327
- counts.total = TASK_STATUSES2.reduce((sum, status2) => sum + counts[status2], 0);
57875
+ counts.total = TASK_STATUSES2.reduce((sum, status3) => sum + counts[status3], 0);
56328
57876
  const projectRows = listProjects(d).filter((project) => !options.project_id || project.id === options.project_id).slice(0, limit).map((project) => ({
56329
57877
  id: project.id,
56330
57878
  name: project.name,
@@ -57525,12 +59073,12 @@ function captureGit(root, warnings) {
57525
59073
  }
57526
59074
  const branch = runLocalCommand(root, ["git", "branch", "--show-current"]);
57527
59075
  const commit = runLocalCommand(root, ["git", "rev-parse", "HEAD"]);
57528
- const status2 = runLocalCommand(root, ["git", "status", "--porcelain=v1"]);
59076
+ const status3 = runLocalCommand(root, ["git", "status", "--porcelain=v1"]);
57529
59077
  if (commit.exitCode !== 0)
57530
59078
  warnings.push(`git commit unavailable: ${commit.stderr || commit.stdout || "unknown error"}`);
57531
- if (status2.exitCode !== 0)
57532
- warnings.push(`git status unavailable: ${status2.stderr || status2.stdout || "unknown error"}`);
57533
- const lines = status2.stdout ? status2.stdout.split(/\r?\n/).filter(Boolean) : [];
59079
+ if (status3.exitCode !== 0)
59080
+ warnings.push(`git status unavailable: ${status3.stderr || status3.stdout || "unknown error"}`);
59081
+ const lines = status3.stdout ? status3.stdout.split(/\r?\n/).filter(Boolean) : [];
57534
59082
  return {
57535
59083
  present: true,
57536
59084
  branch: branch.stdout || null,
@@ -57572,8 +59120,8 @@ function defaultSnapshotDir() {
57572
59120
  return join18(dirname15(resolve17(dbPath)), "environment-snapshots");
57573
59121
  }
57574
59122
  function snapshotWithId(snapshot) {
57575
- const digest2 = sha2566(JSON.stringify(snapshot)).slice(0, 24);
57576
- return { id: `env_${digest2}`, ...snapshot };
59123
+ const digest3 = sha2566(JSON.stringify(snapshot)).slice(0, 24);
59124
+ return { id: `env_${digest3}`, ...snapshot };
57577
59125
  }
57578
59126
  function captureEnvironmentSnapshot(input = {}) {
57579
59127
  const root = resolve17(input.root || process.cwd());
@@ -57773,7 +59321,7 @@ function createDecisionRecord(input, db) {
57773
59321
  const ts = now();
57774
59322
  const sequenceNum = nextSequenceNum(input.project_id, d);
57775
59323
  const shortRef = buildShortRef(input.project_id, sequenceNum, d);
57776
- const status2 = input.status ?? "proposed";
59324
+ const status3 = input.status ?? "proposed";
57777
59325
  d.run(`INSERT INTO decision_records (
57778
59326
  id, project_id, task_id, plan_id, agent_id, sequence_num, short_ref,
57779
59327
  title, status, context, decision, consequences, alternatives, tags,
@@ -57787,7 +59335,7 @@ function createDecisionRecord(input, db) {
57787
59335
  sequenceNum,
57788
59336
  shortRef,
57789
59337
  input.title,
57790
- status2,
59338
+ status3,
57791
59339
  input.context ?? null,
57792
59340
  input.decision,
57793
59341
  input.consequences ?? null,
@@ -57865,15 +59413,15 @@ function updateDecisionRecord(id, input, db) {
57865
59413
  ]);
57866
59414
  return getDecisionRecord(id, d);
57867
59415
  }
57868
- function setDecisionStatus(id, status2, db) {
57869
- if (!DECISION_STATUSES.includes(status2)) {
57870
- throw new Error(`Invalid decision status: ${status2}`);
59416
+ function setDecisionStatus(id, status3, db) {
59417
+ if (!DECISION_STATUSES.includes(status3)) {
59418
+ throw new Error(`Invalid decision status: ${status3}`);
57871
59419
  }
57872
59420
  const d = db || getDatabase();
57873
59421
  const existing = getDecisionRecord(id, d);
57874
59422
  if (!existing)
57875
59423
  throw new Error(`Decision record not found: ${id}`);
57876
- d.run(`UPDATE decision_records SET status = ?, updated_at = ? WHERE id = ?`, [status2, now(), id]);
59424
+ d.run(`UPDATE decision_records SET status = ?, updated_at = ? WHERE id = ?`, [status3, now(), id]);
57877
59425
  return getDecisionRecord(id, d);
57878
59426
  }
57879
59427
  function supersedeDecisionRecord(id, input, db) {
@@ -58154,17 +59702,17 @@ function buildReportExportData(input, db) {
58154
59702
  throw new Error("Project not found");
58155
59703
  projectId = project.id;
58156
59704
  title = `Project: ${project.name}`;
58157
- const status2 = getStatus({ project_id: project.id }, undefined, undefined, d);
59705
+ const status3 = getStatus({ project_id: project.id }, undefined, undefined, d);
58158
59706
  sections.push(section("overview", "Overview", [
58159
59707
  `Name: ${project.name}`,
58160
59708
  `Path: ${project.path}`,
58161
59709
  `Prefix: ${project.task_prefix ?? "\u2014"}`
58162
59710
  ]), section("status", "Status", [
58163
- `Pending: ${status2.pending}`,
58164
- `In progress: ${status2.in_progress}`,
58165
- `Completed: ${status2.completed}`,
58166
- `Total: ${status2.total}`,
58167
- `Stale: ${status2.stale_count}`
59711
+ `Pending: ${status3.pending}`,
59712
+ `In progress: ${status3.in_progress}`,
59713
+ `Completed: ${status3.completed}`,
59714
+ `Total: ${status3.total}`,
59715
+ `Stale: ${status3.stale_count}`
58168
59716
  ]));
58169
59717
  const tasks = listTasks({ project_id: project.id, limit: 50 }, d);
58170
59718
  sections.push(section("tasks", "Recent tasks", tasks.length ? tasks.map(taskLine2) : ["No tasks"]));
@@ -58985,8 +60533,8 @@ function getGitStatus(root, branch, includeGitStatus) {
58985
60533
  return { has_git: false, current_branch: null, branch_exists: false, dirty_files: [] };
58986
60534
  const currentBranch = runGit3(root, ["rev-parse", "--abbrev-ref", "HEAD"]);
58987
60535
  const branchExists = runGit3(root, ["show-ref", "--verify", `refs/heads/${branch}`]) !== null;
58988
- const status2 = runGit3(root, ["status", "--short"]) || "";
58989
- const dirtyFiles = status2.split(`
60536
+ const status3 = runGit3(root, ["status", "--short"]) || "";
60537
+ const dirtyFiles = status3.split(`
58990
60538
  `).map((line) => line.trim()).filter(Boolean).map((line) => normalizePath6(line.replace(/^.. /, "").replace(/^.* -> /, ""))).filter((path) => Boolean(path));
58991
60539
  return { has_git: true, current_branch: currentBranch || null, branch_exists: branchExists, dirty_files: dirtyFiles };
58992
60540
  }
@@ -59384,8 +60932,8 @@ var AGENT_WORKFLOW_DEMO_SCHEMA = "todos.agent_workflow_demo.v1";
59384
60932
  var DEMO_DEFAULT_AGENT = "demoagent";
59385
60933
  var DEMO_DEFAULT_PROJECT = "Agent Workflow Demo";
59386
60934
  var DEMO_PROJECT_PATH = "/tmp/todos-agent-workflow-demo";
59387
- function pushStep(steps, action, detail, status2 = "ok") {
59388
- steps.push({ step: steps.length + 1, action, detail, status: status2 });
60935
+ function pushStep(steps, action, detail, status3 = "ok") {
60936
+ steps.push({ step: steps.length + 1, action, detail, status: status3 });
59389
60937
  }
59390
60938
  function setupEphemeralDemoDb(options = {}) {
59391
60939
  const prevPath = process.env["TODOS_DB_PATH"];
@@ -59512,7 +61060,7 @@ function runAgentWorkflowDemo(options = {}) {
59512
61060
  pushStep(steps, "complete_run_record", `Run record status=${finishedRecord.status}`);
59513
61061
  completeTask(third.id, agent.id, d, { notes: "Demo: finished via agent run + run record" });
59514
61062
  pushStep(steps, "complete_task", `Completed ${third.short_id ?? third.id.slice(0, 8)}`);
59515
- const status2 = getStatus({ project_id: project.id }, agent.id, undefined, d);
61063
+ const status3 = getStatus({ project_id: project.id }, agent.id, undefined, d);
59516
61064
  const tasks = listTasks({ project_id: project.id }, d).map((t) => ({
59517
61065
  id: t.id,
59518
61066
  short_id: t.short_id,
@@ -59544,14 +61092,14 @@ function runAgentWorkflowDemo(options = {}) {
59544
61092
  objective: finishedRecord.objective
59545
61093
  },
59546
61094
  status_summary: {
59547
- pending: status2.pending,
59548
- in_progress: status2.in_progress,
59549
- completed: status2.completed,
59550
- total: status2.total
61095
+ pending: status3.pending,
61096
+ in_progress: status3.in_progress,
61097
+ completed: status3.completed,
61098
+ total: status3.total
59551
61099
  },
59552
61100
  steps,
59553
61101
  completed_at: exportedAt2,
59554
- message: `Demo complete: ${status2.completed}/${status2.total} tasks done, 1 agent run, 1 run record`
61102
+ message: `Demo complete: ${status3.completed}/${status3.total} tasks done, 1 agent run, 1 run record`
59555
61103
  };
59556
61104
  } finally {
59557
61105
  if (dbHandle)
@@ -60525,8 +62073,8 @@ function rowToOrg(row) {
60525
62073
  function createOrg(input, db) {
60526
62074
  const d = db || getDatabase();
60527
62075
  const id = uuid();
60528
- const timestamp4 = now();
60529
- d.run(`INSERT INTO orgs (id, name, description, metadata, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`, [id, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp4, timestamp4]);
62076
+ const timestamp5 = now();
62077
+ d.run(`INSERT INTO orgs (id, name, description, metadata, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`, [id, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp5, timestamp5]);
60530
62078
  return getOrg(id, d);
60531
62079
  }
60532
62080
  function getOrg(id, db) {
@@ -60774,10 +62322,10 @@ function getCriticalPath2(opts, db) {
60774
62322
  function addKgEdge(sourceId, sourceType, targetId, targetType, relationType, weight = 1, metadata, db) {
60775
62323
  const d = db || getDatabase();
60776
62324
  const id = uuid();
60777
- const timestamp4 = now();
62325
+ const timestamp5 = now();
60778
62326
  d.run(`INSERT OR IGNORE INTO kg_edges (id, source_id, source_type, target_id, target_type, relation_type, weight, metadata, created_at)
60779
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, sourceId, sourceType, targetId, targetType, relationType, weight, JSON.stringify(metadata || {}), timestamp4]);
60780
- return { id, source_id: sourceId, source_type: sourceType, target_id: targetId, target_type: targetType, relation_type: relationType, weight, metadata: metadata || {}, created_at: timestamp4 };
62327
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, sourceId, sourceType, targetId, targetType, relationType, weight, JSON.stringify(metadata || {}), timestamp5]);
62328
+ return { id, source_id: sourceId, source_type: sourceType, target_id: targetId, target_type: targetType, relation_type: relationType, weight, metadata: metadata || {}, created_at: timestamp5 };
60781
62329
  }
60782
62330
  function removeKgEdges(sourceId, targetId, relationType, db) {
60783
62331
  const d = db || getDatabase();
@@ -61677,7 +63225,7 @@ function runSavedSearch(filters = {}, scope = "tasks", db) {
61677
63225
  function saveSearchView(input, db) {
61678
63226
  const d = db || getDatabase();
61679
63227
  const name = normalizeName3(input.name);
61680
- const timestamp4 = now();
63228
+ const timestamp5 = now();
61681
63229
  const existing = getSearchView(name, d);
61682
63230
  if (existing) {
61683
63231
  d.run(`UPDATE saved_search_views
@@ -61686,7 +63234,7 @@ function saveSearchView(input, db) {
61686
63234
  input.description ?? existing.description,
61687
63235
  normalizeScope(input.scope ?? existing.scope),
61688
63236
  JSON.stringify(input.filters ?? existing.filters),
61689
- timestamp4,
63237
+ timestamp5,
61690
63238
  existing.id
61691
63239
  ]);
61692
63240
  return getSearchView(existing.id, d);
@@ -61699,8 +63247,8 @@ function saveSearchView(input, db) {
61699
63247
  input.description ?? null,
61700
63248
  normalizeScope(input.scope),
61701
63249
  JSON.stringify(input.filters ?? {}),
61702
- timestamp4,
61703
- timestamp4
63250
+ timestamp5,
63251
+ timestamp5
61704
63252
  ]);
61705
63253
  return getSearchView(id, d);
61706
63254
  }
@@ -61742,14 +63290,14 @@ function readClaudeTask(dir, filename) {
61742
63290
  function writeClaudeTask(dir, task3) {
61743
63291
  writeJsonFile(join23(dir, `${task3.id}.json`), task3);
61744
63292
  }
61745
- function toClaudeStatus(status2) {
61746
- if (status2 === "pending" || status2 === "in_progress" || status2 === "completed") {
61747
- return status2;
63293
+ function toClaudeStatus(status3) {
63294
+ if (status3 === "pending" || status3 === "in_progress" || status3 === "completed") {
63295
+ return status3;
61748
63296
  }
61749
63297
  return "completed";
61750
63298
  }
61751
- function toSqliteStatus(status2) {
61752
- return status2;
63299
+ function toSqliteStatus(status3) {
63300
+ return status3;
61753
63301
  }
61754
63302
  function readPrefixCounter(dir) {
61755
63303
  const path = join23(dir, ".prefix-counter");
@@ -62710,7 +64258,7 @@ init_database();
62710
64258
  function logTrace(input, db) {
62711
64259
  const d = db || getDatabase();
62712
64260
  const id = uuid();
62713
- const timestamp4 = now();
64261
+ const timestamp5 = now();
62714
64262
  d.run(`INSERT INTO task_traces (id, task_id, agent_id, trace_type, name, input_summary, output_summary, duration_ms, tokens, cost_usd, created_at)
62715
64263
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
62716
64264
  id,
@@ -62723,7 +64271,7 @@ function logTrace(input, db) {
62723
64271
  input.duration_ms ?? null,
62724
64272
  input.tokens ?? null,
62725
64273
  input.cost_usd ?? null,
62726
- timestamp4
64274
+ timestamp5
62727
64275
  ]);
62728
64276
  return {
62729
64277
  id,
@@ -62736,7 +64284,7 @@ function logTrace(input, db) {
62736
64284
  duration_ms: input.duration_ms ?? null,
62737
64285
  tokens: input.tokens ?? null,
62738
64286
  cost_usd: input.cost_usd ?? null,
62739
- created_at: timestamp4
64287
+ created_at: timestamp5
62740
64288
  };
62741
64289
  }
62742
64290
  function getTaskTraces(taskId, db) {
@@ -62760,7 +64308,7 @@ init_database();
62760
64308
  init_tasks();
62761
64309
  function setBudget(agentId, opts, db) {
62762
64310
  const d = db || getDatabase();
62763
- const timestamp4 = now();
64311
+ const timestamp5 = now();
62764
64312
  d.run(`INSERT INTO agent_budgets (agent_id, max_concurrent, max_cost_usd, max_task_minutes, period_hours, created_at, updated_at)
62765
64313
  VALUES (?, ?, ?, ?, ?, ?, ?)
62766
64314
  ON CONFLICT(agent_id) DO UPDATE SET
@@ -62774,13 +64322,13 @@ function setBudget(agentId, opts, db) {
62774
64322
  opts.max_cost_usd ?? null,
62775
64323
  opts.max_task_minutes ?? null,
62776
64324
  opts.period_hours ?? 24,
62777
- timestamp4,
62778
- timestamp4,
64325
+ timestamp5,
64326
+ timestamp5,
62779
64327
  opts.max_concurrent ?? null,
62780
64328
  opts.max_cost_usd ?? null,
62781
64329
  opts.max_task_minutes ?? null,
62782
64330
  opts.period_hours ?? null,
62783
- timestamp4
64331
+ timestamp5
62784
64332
  ]);
62785
64333
  return getBudget(agentId, d);
62786
64334
  }
@@ -63083,11 +64631,11 @@ function resolveWorkflowState(input, projectPath) {
63083
64631
  return { input, state: byCanonical, matched_by: "canonical_status" };
63084
64632
  throw new Error(`Unknown workflow state: ${input}`);
63085
64633
  }
63086
- function stateForCanonicalStatus(status2, projectPath) {
63087
- const configured = (getWorkflowConfig(projectPath)?.states || []).map(normalizeState).find((state) => Boolean(state && state.canonical_status === status2));
64634
+ function stateForCanonicalStatus(status3, projectPath) {
64635
+ const configured = (getWorkflowConfig(projectPath)?.states || []).map(normalizeState).find((state) => Boolean(state && state.canonical_status === status3));
63088
64636
  if (configured)
63089
64637
  return configured;
63090
- return listWorkflowStates(projectPath).find((state) => state.canonical_status === status2) || resolveWorkflowState(status2, projectPath).state;
64638
+ return listWorkflowStates(projectPath).find((state) => state.canonical_status === status3) || resolveWorkflowState(status3, projectPath).state;
63091
64639
  }
63092
64640
  function getTaskWorkflowState(taskId, db, projectPath) {
63093
64641
  const d = db || getDatabase();
@@ -63251,9 +64799,9 @@ function unpackFixture(input) {
63251
64799
  const contextPack = input["context_pack"];
63252
64800
  return isObject(contextPack) ? contextPack : input;
63253
64801
  }
63254
- function approvalStatus(status2) {
63255
- if (status2 === "approved" || status2 === "rejected" || status2 === "expired")
63256
- return status2;
64802
+ function approvalStatus(status3) {
64803
+ if (status3 === "approved" || status3 === "rejected" || status3 === "expired")
64804
+ return status3;
63257
64805
  return "pending";
63258
64806
  }
63259
64807
  function approvalsFromFixture(fixture, runs) {
@@ -63350,17 +64898,17 @@ function simulateAgentReplay(input, options = {}) {
63350
64898
  if (["completed", "failed", "cancelled"].includes(runStatus))
63351
64899
  currentStatus = runStatus;
63352
64900
  for (const command of asArray2(run["commands"])) {
63353
- const status2 = normalizedStatus(command["status"]);
64901
+ const status3 = normalizedStatus(command["status"]);
63354
64902
  const commandText = text(command["command"]) || "command";
63355
64903
  push({
63356
64904
  type: "command",
63357
64905
  source: "run.commands",
63358
64906
  label: commandText,
63359
- status: status2,
64907
+ status: status3,
63360
64908
  command: commandText,
63361
64909
  message: text(command["output_summary"])
63362
64910
  });
63363
- if (status2 === "failed")
64911
+ if (status3 === "failed")
63364
64912
  failures.push({ source: "run.commands", message: text(command["output_summary"]) || "command failed", command: commandText });
63365
64913
  }
63366
64914
  for (const file of asArray2(run["files"])) {
@@ -63377,17 +64925,17 @@ function simulateAgentReplay(input, options = {}) {
63377
64925
  }
63378
64926
  }
63379
64927
  for (const verification2 of verifications) {
63380
- const status2 = normalizedStatus(verification2["status"]);
64928
+ const status3 = normalizedStatus(verification2["status"]);
63381
64929
  const commandText = text(verification2["command"]) || "verification";
63382
64930
  push({
63383
64931
  type: "command",
63384
64932
  source: "traceability.verifications",
63385
64933
  label: commandText,
63386
- status: status2,
64934
+ status: status3,
63387
64935
  command: commandText,
63388
64936
  message: text(verification2["output_summary"])
63389
64937
  });
63390
- if (status2 === "failed")
64938
+ if (status3 === "failed")
63391
64939
  failures.push({ source: "traceability.verifications", message: text(verification2["output_summary"]) || "verification failed", command: commandText });
63392
64940
  }
63393
64941
  const approvals = approvalsFromFixture(fixture, runs);
@@ -63923,7 +65471,7 @@ function installLocalExtension(input) {
63923
65471
  const validation = inspected.validation;
63924
65472
  if (!validation.ok)
63925
65473
  throw new Error(`extension manifest invalid: ${validation.errors.join("; ")}`);
63926
- const timestamp4 = new Date().toISOString();
65474
+ const timestamp5 = new Date().toISOString();
63927
65475
  const config = loadConfig();
63928
65476
  const existing = config.extension_registry?.[inspected.manifest.name];
63929
65477
  const trusted = Boolean(input.trust);
@@ -63943,8 +65491,8 @@ function installLocalExtension(input) {
63943
65491
  ...trusted ? [] : ["extension installed as needs_review until trusted explicitly"]
63944
65492
  ],
63945
65493
  diagnostics: testExtensionCompatibility(inspected.source),
63946
- installed_at: existing?.installed_at || timestamp4,
63947
- updated_at: timestamp4
65494
+ installed_at: existing?.installed_at || timestamp5,
65495
+ updated_at: timestamp5
63948
65496
  };
63949
65497
  saveConfig({
63950
65498
  ...config,
@@ -64306,9 +65854,9 @@ async function sendToTmux(target, message, delayMs, options = false) {
64306
65854
  ensureTmuxAvailable();
64307
65855
  if (!opts.confirmBusy) {
64308
65856
  const pane = await inspectTmuxPane(target);
64309
- const status2 = tmuxPaneBusyStatus(pane);
64310
- if (status2.busy) {
64311
- throw new Error(`tmux target "${target}" appears busy (${status2.reason}). Re-run with --confirm-busy to send anyway.`);
65857
+ const status3 = tmuxPaneBusyStatus(pane);
65858
+ if (status3.busy) {
65859
+ throw new Error(`tmux target "${target}" appears busy (${status3.reason}). Re-run with --confirm-busy to send anyway.`);
64312
65860
  }
64313
65861
  }
64314
65862
  const sendProc = Bun.spawn(["tmux", "send-keys", "-t", target, message, ""], {
@@ -64453,10 +66001,10 @@ function normalizeStatus(value) {
64453
66001
  return "open";
64454
66002
  }
64455
66003
  function normalizeResolutionStatus(value) {
64456
- const status2 = normalizeStatus(value || "resolved");
64457
- if (status2 === "open")
66004
+ const status3 = normalizeStatus(value || "resolved");
66005
+ if (status3 === "open")
64458
66006
  throw new Error("resolve-missing status must be resolved or ignored");
64459
- return status2;
66007
+ return status3;
64460
66008
  }
64461
66009
  function redactOptional(value, max = 2000) {
64462
66010
  if (!value)
@@ -64494,7 +66042,7 @@ function compactFinding(finding2) {
64494
66042
  metadata_keys: Object.keys(finding2.metadata).sort()
64495
66043
  };
64496
66044
  }
64497
- function previewFinding(existing, next, timestamp4) {
66045
+ function previewFinding(existing, next, timestamp5) {
64498
66046
  return {
64499
66047
  ...existing,
64500
66048
  run_id: next.run_id,
@@ -64505,9 +66053,9 @@ function previewFinding(existing, next, timestamp4) {
64505
66053
  summary: next.summary,
64506
66054
  artifact_path: next.artifact_path,
64507
66055
  metadata: next.metadata,
64508
- last_seen_at: timestamp4,
64509
- resolved_at: next.status === "open" ? null : existing.resolved_at || timestamp4,
64510
- updated_at: timestamp4
66056
+ last_seen_at: timestamp5,
66057
+ resolved_at: next.status === "open" ? null : existing.resolved_at || timestamp5,
66058
+ updated_at: timestamp5
64511
66059
  };
64512
66060
  }
64513
66061
  function upsertAction(existing, next) {
@@ -64557,7 +66105,7 @@ function sameFinding(left, right) {
64557
66105
  function upsertTaskFinding(input, db) {
64558
66106
  const d = db || getDatabase();
64559
66107
  assertTask(input.task_id, d);
64560
- const timestamp4 = input.observed_at || now();
66108
+ const timestamp5 = input.observed_at || now();
64561
66109
  const warnings = [];
64562
66110
  const next = nextFinding(input, d);
64563
66111
  const existing = getFindingByFingerprint(input.task_id, next.fingerprint, d);
@@ -64568,10 +66116,10 @@ function upsertTaskFinding(input, db) {
64568
66116
  schema_version: TASK_FINDING_UPSERT_SCHEMA_VERSION,
64569
66117
  local_only: true,
64570
66118
  dry_run: true,
64571
- processed_at: timestamp4,
66119
+ processed_at: timestamp5,
64572
66120
  action: action2,
64573
66121
  fingerprint: next.fingerprint,
64574
- finding: existing ? compactFinding(previewFinding(existing, next, timestamp4)) : null,
66122
+ finding: existing ? compactFinding(previewFinding(existing, next, timestamp5)) : null,
64575
66123
  warnings
64576
66124
  };
64577
66125
  }
@@ -64592,17 +66140,17 @@ function upsertTaskFinding(input, db) {
64592
66140
  next.summary,
64593
66141
  next.artifact_path,
64594
66142
  JSON.stringify(next.metadata),
64595
- timestamp4,
64596
- timestamp4,
64597
- next.status === "open" ? null : timestamp4,
64598
- timestamp4,
64599
- timestamp4
66143
+ timestamp5,
66144
+ timestamp5,
66145
+ next.status === "open" ? null : timestamp5,
66146
+ timestamp5,
66147
+ timestamp5
64600
66148
  ]);
64601
66149
  return {
64602
66150
  schema_version: TASK_FINDING_UPSERT_SCHEMA_VERSION,
64603
66151
  local_only: true,
64604
66152
  dry_run: false,
64605
- processed_at: timestamp4,
66153
+ processed_at: timestamp5,
64606
66154
  action: "created",
64607
66155
  fingerprint: next.fingerprint,
64608
66156
  finding: compactFinding(getFindingByFingerprint(input.task_id, next.fingerprint, d)),
@@ -64614,7 +66162,7 @@ function upsertTaskFinding(input, db) {
64614
66162
  schema_version: TASK_FINDING_UPSERT_SCHEMA_VERSION,
64615
66163
  local_only: true,
64616
66164
  dry_run: false,
64617
- processed_at: timestamp4,
66165
+ processed_at: timestamp5,
64618
66166
  action: "matched",
64619
66167
  fingerprint: next.fingerprint,
64620
66168
  finding: compactFinding(existing),
@@ -64634,16 +66182,16 @@ function upsertTaskFinding(input, db) {
64634
66182
  next.summary,
64635
66183
  next.artifact_path,
64636
66184
  JSON.stringify(next.metadata),
64637
- timestamp4,
64638
- next.status === "open" ? null : existing.resolved_at || timestamp4,
64639
- timestamp4,
66185
+ timestamp5,
66186
+ next.status === "open" ? null : existing.resolved_at || timestamp5,
66187
+ timestamp5,
64640
66188
  existing.id
64641
66189
  ]);
64642
66190
  return {
64643
66191
  schema_version: TASK_FINDING_UPSERT_SCHEMA_VERSION,
64644
66192
  local_only: true,
64645
66193
  dry_run: false,
64646
- processed_at: timestamp4,
66194
+ processed_at: timestamp5,
64647
66195
  action,
64648
66196
  fingerprint: next.fingerprint,
64649
66197
  finding: compactFinding(getFindingByFingerprint(input.task_id, next.fingerprint, d)),
@@ -64680,8 +66228,8 @@ function listCompactTaskFindings(filter = {}, db) {
64680
66228
  function resolveMissingTaskFindings(input, db) {
64681
66229
  const d = db || getDatabase();
64682
66230
  assertTask(input.task_id, d);
64683
- const timestamp4 = input.resolved_at || now();
64684
- const status2 = normalizeResolutionStatus(input.status);
66231
+ const timestamp5 = input.resolved_at || now();
66232
+ const status3 = normalizeResolutionStatus(input.status);
64685
66233
  const runId = resolveRunForTask(input.run_id, input.task_id, d);
64686
66234
  const present = new Set(input.fingerprints.map(normalizeTaskFindingFingerprint));
64687
66235
  const warnings = [];
@@ -64700,7 +66248,7 @@ function resolveMissingTaskFindings(input, db) {
64700
66248
  schema_version: TASK_FINDING_RESOLVE_MISSING_SCHEMA_VERSION,
64701
66249
  local_only: true,
64702
66250
  dry_run: true,
64703
- processed_at: timestamp4,
66251
+ processed_at: timestamp5,
64704
66252
  action: candidates.length > 0 ? "preview" : "noop",
64705
66253
  task_id: input.task_id,
64706
66254
  source: input.source ? redactOptional(input.source, 120) : null,
@@ -64723,10 +66271,10 @@ function resolveMissingTaskFindings(input, db) {
64723
66271
  const tx = d.transaction(() => {
64724
66272
  for (const finding2 of candidates) {
64725
66273
  d.run("UPDATE task_findings SET status = ?, resolved_at = ?, metadata = ?, updated_at = ? WHERE id = ? AND status = 'open'", [
64726
- status2,
64727
- timestamp4,
66274
+ status3,
66275
+ timestamp5,
64728
66276
  JSON.stringify({ ...finding2.metadata, ...metadataPatch }),
64729
- timestamp4,
66277
+ timestamp5,
64730
66278
  finding2.id
64731
66279
  ]);
64732
66280
  }
@@ -64738,8 +66286,8 @@ function resolveMissingTaskFindings(input, db) {
64738
66286
  schema_version: TASK_FINDING_RESOLVE_MISSING_SCHEMA_VERSION,
64739
66287
  local_only: true,
64740
66288
  dry_run: false,
64741
- processed_at: timestamp4,
64742
- action: updated.length > 0 ? status2 : "noop",
66289
+ processed_at: timestamp5,
66290
+ action: updated.length > 0 ? status3 : "noop",
64743
66291
  task_id: input.task_id,
64744
66292
  source: input.source ? redactOptional(input.source, 120) : null,
64745
66293
  run_id: runId,
@@ -65632,7 +67180,7 @@ init_tasks();
65632
67180
  init_task_relations();
65633
67181
  init_config2();
65634
67182
  var LOCAL_CAPACITY_SCHEMA_VERSION = 1;
65635
- function timestamp4() {
67183
+ function timestamp5() {
65636
67184
  return new Date().toISOString();
65637
67185
  }
65638
67186
  function todayIso() {
@@ -65716,7 +67264,7 @@ function upsertCapacityProfile(input) {
65716
67264
  const store = readStore2();
65717
67265
  const key2 = profileKey(agentId, projectId);
65718
67266
  const existing = store.profiles[key2];
65719
- const now4 = timestamp4();
67267
+ const now4 = timestamp5();
65720
67268
  const profile = {
65721
67269
  id: existing?.id ?? key2,
65722
67270
  agent_id: agentId,
@@ -65797,7 +67345,7 @@ function getPlanningForecast(input = {}, db) {
65797
67345
  return {
65798
67346
  schema_version: LOCAL_CAPACITY_SCHEMA_VERSION,
65799
67347
  local_only: true,
65800
- generated_at: timestamp4(),
67348
+ generated_at: timestamp5(),
65801
67349
  filters: {
65802
67350
  project_id: input.project_id ?? null,
65803
67351
  plan_id: input.plan_id ?? null,
@@ -66185,6 +67733,8 @@ export {
66185
67733
  testTerminalNotificationRule,
66186
67734
  testLocalEventHook,
66187
67735
  tasksFromTemplate,
67736
+ taskSubtreeTransferRollbackRequestDigest,
67737
+ taskSubtreeTransferRequestDigest,
66188
67738
  taskManifestRequestDigest,
66189
67739
  taskManifestCompensationRequestDigest,
66190
67740
  taskFromTemplate,
@@ -66207,6 +67757,7 @@ export {
66207
67757
  startTaskRun,
66208
67758
  startTask,
66209
67759
  startFocusSession,
67760
+ sqliteTodosTaskSubtreeTransferSchemaSql,
66210
67761
  sqliteTodosTaskManifestSchemaSql,
66211
67762
  sqliteTodosProjectRegistrationSchemaSql,
66212
67763
  softDeleteArtifact,
@@ -66398,6 +67949,7 @@ export {
66398
67949
  previewInstalledTemplate,
66399
67950
  previewInboxIntake,
66400
67951
  previewBuiltinTemplate,
67952
+ postgresTodosTaskSubtreeTransferSchemaSql,
66401
67953
  postgresTodosTaskManifestSchemaSql,
66402
67954
  postgresTodosSyncSchemaSql,
66403
67955
  postgresTodosProjectRegistrationSchemaSql,
@@ -66406,6 +67958,9 @@ export {
66406
67958
  planPlanProjectLink,
66407
67959
  pauseFocusSession,
66408
67960
  patrolTasks,
67961
+ parseTodosTaskSubtreeTransferRollback,
67962
+ parseTodosTaskSubtreeTransferInspect,
67963
+ parseTodosTaskSubtreeTransferApply,
66409
67964
  parseTodosTaskManifestCompensation,
66410
67965
  parseTodosTaskManifestBindingLookup,
66411
67966
  parseTodosTaskManifest,
@@ -66413,7 +67968,6 @@ export {
66413
67968
  parseTodosAiOutputSchema,
66414
67969
  parseTodosAiJson,
66415
67970
  parseTmuxTarget,
66416
- parseStorageMode,
66417
67971
  parseStorageBackend,
66418
67972
  parseRecurrenceRule,
66419
67973
  parseQuietHours,
@@ -66564,7 +68118,6 @@ export {
66564
68118
  issueToTask,
66565
68119
  isWorktreePath,
66566
68120
  isValidRecurrenceRule,
66567
- isTodosRemoteStorageEnabled,
66568
68121
  isTodosPostgresBackend,
66569
68122
  isTodosAiUpdateTaskResult,
66570
68123
  isTodosAiRuntimeEvent,
@@ -66604,6 +68157,7 @@ export {
66604
68157
  importActivityLog,
66605
68158
  hasSecretFindings,
66606
68159
  hasActiveApiKeys,
68160
+ handleTodosTaskSubtreeTransferHttpRequest,
66607
68161
  handleTodosTaskManifestHttpRequest,
66608
68162
  handleTodosProjectRegistrationHttpRequest,
66609
68163
  getWorkspaceTrustStatus,
@@ -66617,7 +68171,6 @@ export {
66617
68171
  getUnlockImpact,
66618
68172
  getTraceStats,
66619
68173
  getTopologyDocs,
66620
- getTodosStorageMode,
66621
68174
  getTodosStorageEnvName,
66622
68175
  getTodosStorageDatabaseUrl,
66623
68176
  getTodosStorageDatabaseEnv,
@@ -66662,7 +68215,6 @@ export {
66662
68215
  getTaskBoard,
66663
68216
  getTask,
66664
68217
  getStoredHandoffAsPacket,
66665
- getStorageMode,
66666
68218
  getStorageDatabaseUrl,
66667
68219
  getStorageDatabaseEnv,
66668
68220
  getStatus,
@@ -66921,6 +68473,9 @@ export {
66921
68473
  detectInboxSourceType,
66922
68474
  detectCyclesFromEdges,
66923
68475
  describeTerminalNotificationRule,
68476
+ deriveTodosTaskSubtreeTransferRollbackPreconditionDigest,
68477
+ deriveTodosTaskSubtreeTransferIdempotencyKey,
68478
+ deriveTodosTaskSubtreeTransferApplyPreconditionDigest,
66924
68479
  deriveTodosTaskManifestIdempotencyKey,
66925
68480
  deriveTodosTaskManifestCompensationPreconditionDigest,
66926
68481
  deriveTodosTaskManifestApplyPreconditionDigest,
@@ -66957,6 +68512,7 @@ export {
66957
68512
  createVerificationEvidence,
66958
68513
  createUserScaffold,
66959
68514
  createTuiDashboardSnapshot,
68515
+ createTodosTaskSubtreeTransferHttpClient,
66960
68516
  createTodosTaskManifestHttpClient,
66961
68517
  createTodosStorageAdapter,
66962
68518
  createTodosS3ArtifactStore,
@@ -66971,6 +68527,7 @@ export {
66971
68527
  createTaskList,
66972
68528
  createTaskBoard,
66973
68529
  createTask,
68530
+ createSqliteTodosTaskSubtreeTransferAuthority,
66974
68531
  createSqliteTodosTaskManifestAuthority,
66975
68532
  createSessionRecoveryHandoff,
66976
68533
  createSession,
@@ -66986,6 +68543,7 @@ export {
66986
68543
  createReminder,
66987
68544
  createReleaseCompatibilityReport,
66988
68545
  createProject,
68546
+ createPostgresTodosTaskSubtreeTransferAuthority,
66989
68547
  createPostgresTodosTaskManifestAuthority,
66990
68548
  createPostgresTodosSyncStore,
66991
68549
  createPostgresTodosStorageAdapter,
@@ -67154,6 +68712,8 @@ export {
67154
68712
  VersionConflictError,
67155
68713
  VERIFICATION_EVIDENCE_SCHEMA,
67156
68714
  USER_SCAFFOLD_SCHEMA,
68715
+ TodosTaskSubtreeTransferHttpClient,
68716
+ TodosTaskSubtreeTransferError,
67157
68717
  TodosTaskManifestHttpClient,
67158
68718
  TodosTaskManifestError,
67159
68719
  TodosProjectRegistrationHttpClient,
@@ -67165,6 +68725,8 @@ export {
67165
68725
  TaskNotFoundError,
67166
68726
  TaskListNotFoundError,
67167
68727
  TUI_DASHBOARD_VIEWS,
68728
+ TODOS_TASK_SUBTREE_TRANSFER_SCHEMA_VERSION,
68729
+ TODOS_TASK_SUBTREE_TRANSFER_ROUTE,
67168
68730
  TODOS_TASK_ROUTE_STATE_SCHEMA_VERSION,
67169
68731
  TODOS_TASK_MANIFEST_SCHEMA_VERSION,
67170
68732
  TODOS_TASK_MANIFEST_ROUTE,
@@ -67271,6 +68833,7 @@ export {
67271
68833
  PostgresTodosProjectRegistrationBackend,
67272
68834
  PlanProjectLinkError,
67273
68835
  PlanNotFoundError,
68836
+ PackageOwnedTodosTaskSubtreeTransferAuthority,
67274
68837
  PackageOwnedTodosTaskManifestAuthority,
67275
68838
  PackageOwnedTodosProjectRegistrationAuthority,
67276
68839
  PROJECT_DEPENDENCY_GRAPH_SCHEMA,